通过反编译字节码来理解 Java Enum

枚举的声明很简单, 像 enum Gender { Male, Female }, 其余事情就是 Java 编译器帮我们干的了,所以 enum 也就是一块语法糖。有了枚举确实是很方便,避免了传统常量的无范围性。那么编译器到底在后面做了什么呢?以及理解了这个之后我们可以怎么去使用 Java 的枚举, 下面就从这个例子说起:

  1. public enum Gender {  
  2.     Male,  
  3.     Female  
  4. }  
public enum Gender {
    Male,
    Female
}

把上面的编译成 Gender.class, 然后用  javap -c Gender 反编译出来就是

  1. public final class Gender extends java.lang.Enum {  
  2.   public static final Gender Male;  
  3.    
  4.   public static final Gender Female;  
  5.    
  6.   public static Gender[] values();  
  7.     Code:  
  8.        0: getstatic     #1                  // Field $VALUES:[LGender;  
  9.        3: invokevirtual #2                  // Method "[LGender;".clone:()Ljava/lang/Object;  
  10.        6: checkcast     #3                  // class "[LGender;"  
  11.        9: areturn  
  12.    
  13.   public static Gender valueOf(java.lang.String);  
  14.     Code:  
  15.        0: ldc_w         #4                  // class Gender  
  16.        3: aload_0  
  17.        4: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;  
  18.        7: checkcast     #4                  // class Gender  
  19.       10: areturn  
  20.    
  21.   static {};  
  22.     Code:  
  23.        0new           #4                  // class Gender  
  24.        3: dup  
  25.        4: ldc           #7                  // String Male  
  26.        6: iconst_0  
  27.        7: invokespecial #8                  // Method "":(Ljava/lang/String;I)V  
  28.       10: putstatic     #9                  // Field Male:LGender;  
  29.       13new           #4                  // class Gender  
  30.       16: dup  
  31.       17: ldc           #10                 // String Female  
  32.       19: iconst_1  
  33.       20: invokespecial #8                  // Method "":(Ljava/lang/String;I)V  
  34.       23: putstatic     #11                 // Field Female:LGender;  
  35.       26: iconst_2  
  36.       27: anewarray     #4                  // class Gender  
  37.       30: dup  
  38.       31: iconst_0  
  39.       32: getstatic     #9                  // Field Male:LGender;  
  40.       35: aastore  
  41.       36: dup  
  42.       37: iconst_1  
  43.       38: getstatic     #11                 // Field Female:LGender;  
  44.       41: aastore  
  45.       42: putstatic     #1                  // Field $VALUES:[LGender;  
  46.       45return  
  47. }  
public final class Gender extends java.lang.Enum {
  public static final Gender Male;
 
  public static final Gender Female;
 
  public static Gender[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[LGender;
       3: invokevirtual #2                  // Method "[LGender;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[LGender;"
       9: areturn
 
  public static Gender valueOf(java.lang.String);
    Code:
       0: ldc_w         #4                  // class Gender
       3: aload_0
       4: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       7: checkcast     #4                  // class Gender
      10: areturn
 
  static {};
    Code:
       0: new           #4                  // class Gender
       3: dup
       4: ldc           #7                  // String Male
       6: iconst_0
       7: invokespecial #8                  // Method "":(Ljava/lang/String;I)V
      10: putstatic     #9                  // Field Male:LGender;
      13: new           #4                  // class Gender
      16: dup
      17: ldc           #10                 // String Female
      19: iconst_1
      20: invokespecial #8                  // Method "":(Ljava/lang/String;I)V
      23: putstatic     #11                 // Field Female:LGender;
      26: iconst_2
      27: anewarray     #4                  // class Gender
      30: dup
      31: iconst_0
      32: getstatic     #9                  // Field Male:LGender;
      35: aastore
      36: dup
      37: iconst_1
      38: getstatic     #11                 // Field Female:LGender;
      41: aastore
      42: putstatic     #1                  // Field $VALUES:[LGender;
      45: return
}

从上面的字节码能够粗略的知道以下几点

  1. Gender 是 final 的
  2. Gender 继承自 java.lang.Enum 类
  3. 声明了字段对应的两个 static final Gender 的实例
  4. 实现了 values() 和  valueOf(String) 静态方法
  5. static{} 对所有成员进行初始化

有了以上的字节码,我们作进一步还原出 Gender 的普通类大概是这样的:

  1. public final class Gender extends java.lang.Enum {  
  2.    
  3.   public static final Gender Male;  
  4.   public static final Gender Female;  
  5.    
  6.   private static final Gender[] $VALUES;  
  7.    
  8.   static {  
  9.     Male = new Gender("Male"0);  
  10.     Female = new Gender("Female"1);  
  11.    
  12.     $VALUES = new Gender[] {Male, Female};  
  13.   }  
  14.    
  15.   private Gender(String name, int original) {  
  16.     super(name, original)  
  17.   }  
  18.    
  19.   public static Gender[] values() {  
  20.     return $VALUE.clone();  
  21.   }  
  22.    
  23.   public static Gender valueOf(String name) {  
  24.     return Enum.valueOf(Gender.class, name);  
  25.   }  
  26. }  
public final class Gender extends java.lang.Enum {
 
  public static final Gender Male;
  public static final Gender Female;
 
  private static final Gender[] $VALUES;
 
  static {
    Male = new Gender("Male", 0);
    Female = new Gender("Female", 1);
 
    $VALUES = new Gender[] {Male, Female};
  }
 
  private Gender(String name, int original) {
    super(name, original)
  }
 
  public static Gender[] values() {
    return $VALUE.clone();
  }
 
  public static Gender valueOf(String name) {
    return Enum.valueOf(Gender.class, name);
  }
}

private Gender(String name, int origianl) 是我加上去的,是为了模拟枚举实例的创建,其实实例都是在 static 块中创建的。

当然上面的那个类是无法被编译的,因为 Java  编译器限制了我们显式的继承自 java.Lang.Enum 类, 报错 "The type Gender may not subclass Enum explicitly", 虽然 java.Lang.Enum 声明的是

  1. public abstract class Enum>  
  2.         implements Comparable, Serializable  
public abstract class Enum>
        implements Comparable, Serializable

这样看来枚举类其实用了多例模式,枚举类的实例是有范围限制的。它同样像我们的传统常量类,只是它的元素是有限的枚举类本身的实例。它继承自 java.lang.Enum, 所以可以直接调用 java.lang.Enum 的方法,如 name(), original() 等,name 就是常量名称, original 与 C 的枚举一样的编号。

可以在枚举类中自定义构造方法,但必须是  private 或  package protected, 因为枚举本质上是不允许在外面用 new Gender() 方式来构造实例的(Cannot instantiate the type Gender),否则也是要乱了套的。

我们可以在枚举中定义自己的类,或实例方法,比如对于   enum Gender, 方法会直接定义给  Gender 类或实例上去。

由于 enum 隐式继承自 java.lang.Enum 类,且 Java 不接受类的多重继承,所以我们不能让 enum 再继承别的类,下面那样写是错误的

enum Gender extends Security {} //Syntax error on token "extends", implements expected

不能类多重继承,那枚举可以实现接口啊,因此下面的写法是正确的

public enum Gender implements Behavior {}


让枚举实现接口有时候可以让设计变得巧妙。

结合枚举实现接口以及自定义方法,可以写出下面那样的代码:

  1. interface Behavior {  
  2.   void print();  
  3. }  
  4.    
  5. public enum Gender implements Behavior {  
  6.   Male {  
  7.     @Override  
  8.     public void print() {  
  9.       System.out.println("Male special behavior");  
  10.     }  
  11.   },  
  12.   Female;  
  13.      
  14.   @Override  
  15.   public void print() {  
  16.     System.out.println("Common behavior");  
  17.   }  
  18.      
  19.   public static void main(String[] args) {  
  20.     Gender.Male.print(); //Male special behavior  
  21.     Gender.Female.print();  //Common behavior  
  22.   }  
  23. }  
interface Behavior {
  void print();
}
 
public enum Gender implements Behavior {
  Male {
    @Override
    public void print() {
      System.out.println("Male special behavior");
    }
  },
  Female;
   
  @Override
  public void print() {
    System.out.println("Common behavior");
  }
   
  public static void main(String[] args) {
    Gender.Male.print(); //Male special behavior
    Gender.Female.print();  //Common behavior
  }
}

方法可以定义成所有实例公有,也可以让个别元素独有。

需要特别注明一下,上面在 Male {} 声明一个 print() 方法后实际产生一个 Gender 的匿名子类,编译后的 Gender$1,反编译它 javap -c Gender\$1:

  1. final class Gender$1 extends Gender {  
  2.   Gender$1(java.lang.String, int);  
  3.     Code:  
  4.        0: aload_0  
  5.        1: aload_1  
  6.        2: iload_2  
  7.        3: aconst_null  
  8.        4: invokespecial #1                  // Method Gender."":(Ljava/lang/String;ILGender$1;)V  
  9.        7return  
  10.    
  11.   public void print();  
  12.     Code:  
  13.        0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;  
  14.        3: ldc           #3                  // String Male special behavior  
  15.        5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V  
  16.        8return  
  17. }  
final class Gender$1 extends Gender {
  Gender$1(java.lang.String, int);
    Code:
       0: aload_0
       1: aload_1
       2: iload_2
       3: aconst_null
       4: invokespecial #1                  // Method Gender."":(Ljava/lang/String;ILGender$1;)V
       7: return
 
  public void print();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Male special behavior
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

所以在 emum Gender 那个枚举中的成员  Male 相当于是

  1. public static final Male = new Gender$1("Male"0); //而不是 new Gender("Male", 0)  
public static final Male = new Gender$1("Male", 0); //而不是 new Gender("Male", 0)

上面 4: Invokespecial #1 要调用到下面的 Gender(java.lang.String, int, Gender$1) 方法。

若要研究完整的 Male 元素的初始化过程就得 javap -c Gender 看 Gender.java 产生的所有字节码,在此列出片断

  1. Gender(java.lang.String, int, Gender$1);  
  2.     Code:  
  3.        0: aload_0  
  4.        1: aload_1  
  5.        2: iload_2  
  6.        3: invokespecial #1                  // Method "":(Ljava/lang/String;I)V  
  7.        6return  
  8.    
  9.   static {};  
  10.     Code:  
  11.        0new           #14                 // class Gender$1  
  12.        3: dup  
  13.        4: ldc           #15                 // String Male  
  14.        6: iconst_0  
  15.        7: invokespecial #16                 // Method Gender$1."":(Ljava/lang/String;I)V  
  16.       10: putstatic     #11                 // Field Male:LGender;  
Gender(java.lang.String, int, Gender$1);
    Code:
       0: aload_0
       1: aload_1
       2: iload_2
       3: invokespecial #1                  // Method "":(Ljava/lang/String;I)V
       6: return
 
  static {};
    Code:
       0: new           #14                 // class Gender$1
       3: dup
       4: ldc           #15                 // String Male
       6: iconst_0
       7: invokespecial #16                 // Method Gender$1."":(Ljava/lang/String;I)V
      10: putstatic     #11                 // Field Male:LGender;


在 static{} 中大致看下 Male 的初始过程:加载 Gender$1, 并调用它的 Gender$1(java.lang.String, int) 构造函数生成一个 Gender$1 实例赋给 Male 属性。

JDK 提供有两个关于枚举的工具类  EnumSet 和 EnumMap,  实际中可以好好发掘发掘一下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值