Java Enum Trick(Java枚举实现原理)

       Java 从JDK1.5开始引入了枚举特性,用户可以直接采用与C++类似的方式定义枚举类型。有趣的是,Java 枚举的实现是在字节码编译器层次,并未修改JVM底层。本文主要在字节码层面分析Java Enum的实现方式。代码1在enumtestpkg包中定义了MyColor枚举类型,其包含red,blue两个枚举常量。

 #代码1

package enumtestpkg;

public enum MyColor {
	red,blue
}

 一. java.lang.Enum类介绍

    要了解Java Enum实现方式,就不得不提java.lang包下的Enum类,其类定义如下:

#代码2

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

      所有用户定义的枚举类型都继承自Enum类(稍后将会在字节码中看到隐式继承),它是一个抽象类,并且包含一个继承自该类的类作为泛型参数。一个Enum类对象对应一个枚举常量,Enum类中定义了两个私有常量字段,String name表示用户声明的枚举常量名称;int ordinal表示用户在定义枚举常量时的顺序,通常用户不会用到该字段,其设计是为了用于如EnumSet和EnumMap等基于枚举的复杂数据结构。上述两个字段具有对应的public方法供用户访问。

     Enum中其它重要方法介绍如下(参考JavaDoc):

  •     protected Enum(String name, int ordinal) :唯一构造方法,供编译器使用,用户无法调用。
  •    public final boolean equals(Object other): 其内部直接使用“==”结果作为返回值,即对于Enum对象来说,其equals方法比较的是引用
  •    clone() : 调用此方法会抛出CloneNotSupportException异常,这能够保证枚举常量的单例状态
  •    public final Class<E> getDeclaringClass():获取对应枚举常量的枚举类型。需要强调:两个枚举常量类型相同当且仅当二者该方法返回值相同,不能使用getClass方法作为判断依据。因为枚举常量定义时可以重写枚举类的方法,则此枚举常量getClass方法返回值是内部类对象。 例子如代码3所示:

   #代码3

public enum MyFruit {
	appale,
	orange{
		void print() {
			System.out.println("orange");
			System.out.println(getClass());
			System.out.println(getDeclaringClass());
		}
		
	};
	 void print(){
		 System.out.println("default");
		 System.out.println(getClass());
		 System.out.println(getDeclaringClass());
	 };
}
public class TestEnum {
	public static void main(String[] args) {
		for(MyFruit f:MyFruit.values()){
			f.print();
			System.out.println(f.getDeclaringClass());
		}
	}
}

输出结果如下:

default
class enumtestpkg.MyFruit
class enumtestpkg.MyFruit
orange
class enumtestpkg.MyFruit$1
class enumtestpkg.MyFruit

对于orange常量是MyFruit$1类的对象,该类继承自MyFruit类。getgetDeclaringClass方法会首先检查枚举类的父类是否为Enum类,如果是则返回当前枚举类对象,因此apple返回了MyFruit类。若父类不为Enum类,如orange,则返回其父类对象,因此orange也返回了MyFruit类。使用getDeclaringClass方法能够保证同一枚举类型的常量被判断为类型相同。

  • compareTo(E o) :实现了Comparable接口,内部比较二者的ordinal值
  •   public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) :根据name的值返回对应的泛型常量。注意这个方法将会在用户的泛型类中被隐式调用。
  • readObject、readObject:这两个方法在调用时均会抛出异常,这是为了防止使用这两个方法对枚举类型进行反序列化,这可能造成程序中枚举对象的单例唯一性。Java枚举类型序列化之后会保存各枚举常量的name字段,反序列化时可以使用valueOf(由编译器隐式生成)方法来获取枚举对象。

二.Java枚举实现原理分析

    使用javap工具对代码1编译生成的MyColor.class文件进行反编译,结果如下:

#javap -c MyColor.class

public final class enumtestpkg.MyColor extends java.lang.Enum<enumtestpkg.MyColor> {
  public static final enumtestpkg.MyColor red;
  public static final enumtestpkg.MyColor blue;

  static {};
    Code:
       0: new           #1                  // class enumtestpkg/MyColor
       3: dup
       4: ldc           #13                 // String red
       6: iconst_0
       7: invokespecial #14                 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #18                 // Field red:Lenumtestpkg/MyColor;
      13: new           #1                  // class enumtestpkg/MyColor
      16: dup
      17: ldc           #20                 // String blue
      19: iconst_1
      20: invokespecial #14                 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #21                 // Field blue:Lenumtestpkg/MyColor;
      26: iconst_2
      27: anewarray     #1                  // class enumtestpkg/MyColor
      30: dup
      31: iconst_0
      32: getstatic     #18                 // Field red:Lenumtestpkg/MyColor;
      35: aastore
      36: dup
      37: iconst_1
      38: getstatic     #21                 // Field blue:Lenumtestpkg/MyColor;
      41: aastore
      42: putstatic     #23                 // Field ENUM$VALUES:[Lenumtestpkg/MyColor;
      45: return

  public static enumtestpkg.MyColor[] values();
    Code:
       0: getstatic     #23                 // Field ENUM$VALUES:[Lenumtestpkg/MyColor;
       3: dup
       4: astore_0
       5: iconst_0
       6: aload_0
       7: arraylength
       8: dup
       9: istore_1
      10: anewarray     #1                  // class enumtestpkg/MyColor
      13: dup
      14: astore_2
      15: iconst_0
      16: iload_1
      17: invokestatic  #31                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
      20: aload_2
      21: areturn

  public static enumtestpkg.MyColor valueOf(java.lang.String);
    Code:
       0: ldc           #1                  // class enumtestpkg/MyColor
       2: aload_0
       3: invokestatic  #39                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #1                  // class enumtestpkg/MyColor
       9: areturn
}

从上述字节码中可以得到如下结论:

  1. 第1行:用户定义枚举类型后,将由编译器编译成字节码,生成“枚举类”。在字节码层面,Java使用类来实现枚举功能,这种设计不会影响Java虚拟机;
  2. 第2-3行:用户定义的枚举常量将被设定为枚举类的静态常量字段,所以用户在访问枚举常量的时候可以采用“类名.枚举常量名”进行访问。
  3. 第5-30行:生成一个静态块代码。其中,7-12,13-18行分别调用父类的构造函数(即一中提到的protected构造函数),依次创建了两个枚举对象,并赋值给red和blue,其ordial字段分别设置为0和1(iconst_0,iconst_1). 19-30行则新建了一个大小为2的静态枚举对象数组,并分别将上述red,blue字段添加到数组中。在第29行将该数组赋值给类内部隐式创建的VALUES静态字段。
  4. 第32-49行:隐式创建一个values()方法,该方法内部调用arraycopy方法放回3中创建的枚举类数组的一个复制对象引用。调用该方法可以获得该枚举类型的所有枚举类对象。
  5. 第51-58行:隐式创建一个valueOf(String)方法,该方法调用了一中所说的Enum类的ValueOf方法,返回名称为指定值的枚举类对象。

三.总结

1.Java 枚举在字节码层面使用已有的字节码实现并通过类进行包装;

2.Java枚举常量是单例模式,在反序列化时会被保护。这也是为何equal内部也是比较引用,因为枚举常量有很强的专一指向性。

3.一般意义上,用户定义枚举常量类型时可以实现普通类的功能,可以增加方法和属性。利用这个特点,可以实现类似于C++的在定义枚举常量时跳跃赋数值值(并非修改ordial的值,ordial字节码按严格顺序赋值,用户无法更改)。

参考资料:

           1.Java Enum类源码及注释

          2.Java Language Specifica


附:一个很不友好的枚举使用方式,猜一猜结果

MyFruit fruit=MyFruit.appale.orange;
System.out.println(fruit==MyFruit.appale);


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值