Java魔法堂:枚举类型详解

时间:2022-04-22
本文章向大家介绍Java魔法堂:枚举类型详解,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、前言                                

  Java的枚举类型相对C#来说具有更灵活可配置性,Java的枚举类型可以携带更多的信息。

// C#
enum MyColor{
  RED = 0,
  BLUE = 1
}
Console.Write(MyColor.RED);

// Java
enum MyColor{
  RED("Hot", 4), BLUE("SAD",8);
  
  private String mood;
  public String getMood{
    return mood;
  }
  private int index;
  public int getIndex(){
    return index;
  }
  private MyColor(String mood, int index){
    this.mood = mood;
    this.index = index;
  }
}
System.out.println(MyColor.RED.getMood());

  本文将对枚举类型进行较为详细的叙述,以便日后查阅。

二、最简单的用法——常量                        

/* 定义 */
// 形式1
enum MyColor{
  RED,BLUE
}

// 形式2
enum MyColor{
  RED,BLUE;
} 

/* 使用 */
System.out.println(MyColor.RED.name()); // 显示RED
System.out.println(MyColor.RED.ordinal()); // 显示0
System.out.println(MyColor.BLUE.name()); // 显示BLUE
System.out.println(MyColor.BLUE.ordinal()); // 显示1

  枚举值的name()会返回枚举值的字面量,而ordinal()为返回枚举值的索引,而索引是以枚举值定义时的位置来确定,并在编译时设置的。下面我们来看看到底编译器为我们做了什么?

final class MyColor extends java.lang.Enum<MyCorlor>{
  public static final MyColor RED;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final MyColor BLUE;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static MyColor[] values();
    flags: ACC_PUBLIC, ACC_STATIC

    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #1                  // Field $VALUES:[LMyColor;
         3: invokevirtual #2                  // Method "[LMyColor;".clone:()Ljava/lang/Object;
         6: checkcast     #3                  // class "[LMyColor;"
         9: areturn       
      LineNumberTable:
        line 1: 0

  public static MyClass valueOf(java.lang.String);
    flags: ACC_PUBLIC, ACC_STATIC

    Code:
      stack=2, locals=1, args_size=1
         0: ldc_w         #4                  // class MyColor
         3: aload_0       
         4: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         7: checkcast     #4                  // class MyColor
        10: areturn       
      LineNumberTable:
        line 1: 0

  static {};
    flags: ACC_STATIC

    Code:
      stack=4, locals=0, args_size=0
         0: new           #4                  // class MyColor
         3: dup           
         4: ldc           #7                  // String RED
         6: iconst_0      
         7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        10: putstatic     #9                  // Field RED:LMyColor;
        13: new           #4                  // class MyColor
        16: dup           
        17: ldc           #10                 // String BLUE
        19: iconst_1      
        20: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        23: putstatic     #11                 // Field BLUE:LMyColor;
        26: iconst_2      
        27: anewarray     #4                  // class MyColor
        30: dup           
        31: iconst_0      
        32: getstatic     #9                  // Field RED:LMyColor;
        35: aastore       
        36: dup           
        37: iconst_1      
        38: getstatic     #11                 // Field BLUE:LMyColor;
        41: aastore       
        42: putstatic     #1                  // Field $VALUES:[LMyColor;
        45: return        
      LineNumberTable:
        line 2: 0
        line 1: 26
}

 可以看到编译器将enum MyColor编译为一个继承Enum<MyColor>并且带修饰符final的MyColor类。

 而枚举值RED和BLUE则被编译为MyColor的类常量,并且在类加载的初始化阶段实例化。MyColor默认的构造函数会调用父类Enum<MyColor>的构造函数Enum<E>(String name, int ordinal)来设置私有字段name和ordinal的值。其中iconst_0和iconst_1分别表示将0和1压栈,invokespecial #8则是调用构造函数Enum<E>(String name, int ordinal)。

  0: new           #4                  // class MyColor
  3: dup           
  4: ldc           #7                  // String RED
  6: iconst_0                          // int 0
  7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
  10: putstatic     #9                  // Field RED:LMyColor;
  13: new           #4                  // class MyColor
  16: dup           
  17: ldc           #10                 // String BLUE
  19: iconst_1                          // int 1
  20: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
  23: putstatic     #11                 // Field BLUE:LMyColor;

  另外在类加载的初始化阶段会生成一个私有的$VALUE数组用于存放常量RED和BLUE,而在调用MyColor.values()返回的正是这个$VALUE数组的复制品。

 26: iconst_2      
 27: anewarray     #4                  // class MyColor
 30: dup           
 31: iconst_0      
 32: getstatic     #9                  // Field RED:LMyColor;
 35: aastore       
 36: dup           
 37: iconst_1      
 38: getstatic     #11                 // Field BLUE:LMyColor;
 41: aastore       
 42: putstatic     #1                  // Field $VALUES:[LMyColor;

  小结:

     1. 定义枚举类型本质上就是在定义带final修饰符的Enum<E>的子类;

     2. 枚举值本质为第1点所定义的类的类常量;

     3. 枚举值的ordinal值由其定义时的排序决定,并且在编译时已经被设置好了。

三、枚举类型的抽象父类Enum<E>                    

  其实我们大多数情况下都是调用父类Enum<E>的方法来操作自定义的枚举值,下面一起看看父类Enum<E>吧!

  1. 它为抽象类且继承了Comparable<E>和Serializable两个类。

  2. 内含私有字段name和ordinal和对应的公有get方法name()和ordinal()。

  3. 重写了equals方法,通过==比较两个枚举值的内存地址来判断两者是否相同。

  4. 实现compareTo方法,通过比较两个枚举值的ordinal值来做判断。

  5. getDeclaringClass方法,用于返回枚举的Class对象。

四、携带更多信息——自定义构造函数                   

  由于枚举最终被编译为类,因此我们通过自定义构造函数、自定义字段和方法来让枚举值携带更多信息

public enum MyColor{
  RED("Hot", 2), BLUE("SAD",5);
  
  private String mood;
  private int index;
  private MyColor(String mood, int index){
    this.mood = mood;
    this.index = index;
  }
}

  注意:

    1. 自定义的构造函数必须是私有的;

    2. 构造函数内不能显式调用父类的构造函数;

    3. RED、BLUE的ordinal值依然是0和1,那么值依然是RED和BLUE。

 上述3点规定和结果的原因是编译器会对我们的自定义构造函数进行加工变为

private MyColor(String name, String ordinal, String mood, int index){
  super(name, ordinal);
  this.mood = mood;
  this.index = index;
}

五、让相同枚举类型下的枚举值具有不同的行为——重写枚举值的方法    

public enum MyColor{
  RED, BLUE(){
     @Override
     public boolean getFlag(){
       return false;
     }
  };
  public boolean getFlag(){
    return true;
  }
}
// 调用
System.out.println(MyColor.RED.getFlag()); // 显示true
System.out.println(MyColor.BLUE.getFlag()); // 显示false

  可以看到枚举值RED和BLUE同一个方法具有不同的行为。其实这是通过匿名内部类的方式实现的,BLUE的类型为MyColor$1 extends MyColor,而RED的类型为MyColor。

六、使用接口组织枚举                          

public interface Food {
        enum Coffee implements Food {
            BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO
        }

        enum Dessert implements Food {
            FRUIT, CAKE, GELATO
        }
    }

七、总结                                

  若有纰漏请大家指正,谢谢。

八、参考                                

  http://www.tuicool.com/articles/YvQZFf

  http://www.cnblogs.com/hemingwang0902/archive/2011/12/29/2306263.html

  http://www.cnblogs.com/frankliiu-java/archive/2010/12/07/1898721.html