【java基礎】列舉

Hitechr發表於2021-08-05

列舉的定義

public enum Color {
    Red,Blue,Green;
}

列舉的使用

Color red = Color.Red;//列舉的其中一個型別
Color[] values = Color.values();//獲取所有的列舉型別
String name = red.name();//可以獲得列舉值的名稱
int ordinal = red.ordinal();//可以獲得列舉值的編號

原理

那我們定義列舉型別後,到底發生了什麼呢?我們對列舉的實現原理進行探究。
我們來解析下Color.class檔案,命令javap Color

public final class Color extends java.lang.Enum<Color> {
  public static final Color Red;
  public static final Color Blue;
  public static final Color Green;
  public static final Color[] $VALUES;
  public static Color[] values();
  public static Color valueOf(java.lang.String);
  static {};
}

從解析後的檔案中我們可以知道

  1. 列舉類是final的,不能被繼承
  2. 列舉類在經過編譯後生成一個繼承java.lang.Enum的類Color
  3. 編譯後的列舉值,是該類的Color型別的成員變數,如Red、Bule、Green,並且是final static修飾
  4. 列舉編譯後的類新增了靜態的values()valueOf(java.lang.String)方法
  5. 靜態程式碼塊static {}

進一步細化Color.class
命令javap -c Color

public final class Color extends java.lang.Enum<Color> {
  public static final Color Red;
  public static final Color Blue;
  public static final Color Green;
  public static Color[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[LColor;
       3: invokevirtual #2                  // Method "[LColor;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[LColor;"
       9: areturn

  public static Color valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class Color
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljav
a/lang/Enum;
       6: checkcast     #4                  // class Color
       9: areturn

  static {};
    Code:
       0: new           #4                  // class Color
       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:LColor;
      13: new           #4                  // class Color
      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:LColor;
      26: new           #4                  // class Color
      29: dup
      30: ldc           #12                 // String Green
      32: iconst_2
      33: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
      36: putstatic     #13                 // Field Green:LColor;
      
      39: iconst_3
      40: anewarray     #4                  // class Color
      43: dup
      
      44: iconst_0
      45: getstatic     #9                  // Field Red:LColor;
      48: aastore
      49: dup
      50: iconst_1
      51: getstatic     #11                 // Field Blue:LColor;
      54: aastore
      55: dup
      56: iconst_2
      57: getstatic     #13                 // Field Green:LColor;
      60: aastore
      61: putstatic     #1                  // Field $VALUES:[LColor;
      64: return
}

還原後的程式碼如下:

public final class Color extends java.lang.Enum<Color> {
    //定義的列舉成員
    public static final Color Red;
    public static final Color Blue;
    public static final Color Green;
    //編譯器自動生成的 javap -c 還查不出來,疑惑
    public static final /* synthetic */ Color[] $VALUES;//編譯器自動生成的
    public static Color[] values(){
        /**
         * 0: getstatic     #1                  // Field $VALUES:[LColor;
         * 3: invokevirtual #2                  // Method "[LColor;".clone:()Ljava/lang/Object;
         * 6: checkcast     #3                  // class "[LColor;"
         * 9: areturn
         */
        return $VALUES.clone();
    }
    public static Color valueOf(java.lang.String s){
        /**
         * 0: ldc           #4                  // class Color
         * 2: aload_0
         * 3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljav
         * a/lang/Enum;
         * 6: checkcast     #4                  // class Color
         * 9: areturn
         */
        return Enum.valueOf(Color.class,s);
    }
    protected Color(String name, int ordinal) {
        super(name, ordinal);
    }
    static {
        /**
         * 0: new           #4                  // class Color
         * 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:LColor;
         */
        Red=new Color("Red",0);
        /**
         * 13: new           #4                  // class Color
         * 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:LColor;
         */
        Blue=new Color("Blue",1);
        /**
         * 26: new           #4                  // class Color
         * 29: dup
         * 30: ldc           #12                 // String Green
         * 32: iconst_2
         * 33: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
         * 36: putstatic     #13                 // Field Green:LColor;
         */
        Green=new Color("Green",2);
        /**
         * 39: iconst_3
         * 40: anewarray     #4                  // class Color
         * 43: dup
         * 44: iconst_0
         * 45: getstatic     #9                  // Field Red:LColor;
         * 48: aastore
         * 49: dup
         * 50: iconst_1
         * 51: getstatic     #11                 // Field Blue:LColor;
         * 54: aastore
         * 55: dup
         * 56: iconst_2
         * 57: getstatic     #13                 // Field Green:LColor;
         * 60: aastore
         * 61: putstatic     #1                  // Field $VALUES:[LColor;
         */
        $VALUES=new Color[]{Red,Blue,Green};
    };
}

通過上面還原後的程式碼可知,在類的static程式碼塊中編譯器幫我們生成列舉中的每個成員,實際上就是生成物件並賦值給靜態變數。

列舉的擴充套件

單例模式

列舉其實就是編譯幫我們在靜態程式碼塊中建立一個或多個列舉成員物件,如果我們只定義一個列舉成員,這樣就是一個單例物件

public enum Singleton {
    INSTANCE;
    public void doSometing(){
        System.out.println("doing");
    }
}

程式碼如此簡單,不僅如此,這樣的方式還可以解決反射攻擊反序列化導致的單例失敗的問題。

避免反射攻擊

在獲取例項時進行了列舉判斷,如果是列舉型別的則直接丟擲異常。

Jdk1.8 Class.clss 檔案的416..417

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException{
        ....
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        .....
        return inst;
    }

反序列化

列舉的序列時僅降列舉物件的name屬性輸出到結果中,在反序列化時則是通過java.lang.EnumvalueOf方法來根據名字在jvm中查詢物件的。

同時編譯器還禁止對列舉的定製化序列機制,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

策略模式

一種行為,多種結果,不同結果的實現,名曰策略。

public enum Calculator {
    //加法運算
    ADD("+"){
        public int exec(int a,int b){
            return a+b;
        }
    },
    //減法運算
    SUB("-"){
        public int exec(int a,int b){
            return a - b;
        }
    };
    String value = "";
    //定義成員值型別
    Calculator(String _value){
        this.value = _value;
    }
    //獲得列舉成員的值
    public String getValue(){
        return this.value;
    }
    //宣告一個抽象函式
    public abstract int exec(int a,int b);
}

總結

  1. 列舉其實就是編寫程式碼方面的語法糖,仍然是一個完整的類,暴露了幾個靜態變數,隱藏掉了大部分細節
  2. 列舉的成員變數是在編譯後的靜態程式碼塊逐個建立例項來實現的
  3. 列舉的values()valueOf()方法都是編譯器幫我們建立出來的
  4. 在呼叫values()方法時,返回的物件是克隆出來的一份新的,修改對原物件沒有影響
  5. 列舉可以實現單例模式,能避免反射攻擊和反序列化問題
  6. 避免反射攻擊是因為列舉類不允許通過反射例項化
  7. 反序列化是通過Enum.valueOf到jvm中根據class,name去查詢的
  8. 列舉可以實現策略模式,優化程式碼,避免大量的if...else的出現

相關文章