從JDK角度認識枚舉enum

前言

對於比較穩定的值集合,Java 提供了枚舉來定義,經過它能夠很方便管理集合。那麼 Java 的枚舉是經過怎樣的機制實現的?本文將從 JDK 角度來看看枚舉的原理。java

定義枚舉

使用很簡單,好比定義一個表示「環保」、「交通」、「手機」三個值的集合,那麼就能夠直接定義以下,而後可直接 Labels.ENVIRONMENT 使用,數組

public enum Labels {

  ENVIRONMENT(), TRAFFIC(), PHONE();

}
複製代碼

同時也可使用帶構造函數的枚舉,以下,能夠經過 getName 獲取值。bash

public enum Labels0 {

  ENVIRONMENT("環保"), TRAFFIC("交通"), PHONE("手機");

  private String name;

  private Labels0(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

}

複製代碼

編譯器作了什麼

Java中的枚舉的實現機制是怎樣的?枚舉看起來有點像上帝扔給咱們的語法糖,秉着深刻挖一挖的精神,看看枚舉是相關實現,看看編譯器作了什麼。用 javap 看上面兩個枚舉編譯後的字節碼:併發

public final class com.seaboat.Labels extends java.lang.Enum<com.seaboat.Labels> {
  public static final com.seaboat.Labels ENVIRONMENT;
  public static final com.seaboat.Labels TRAFFIC;
  public static final com.seaboat.Labels PHONE;
  static {};
  public static com.seaboat.Labels[] values();
  public static com.seaboat.Labels valueOf(java.lang.String);
}
複製代碼
public final class com.seaboat.Labels0 extends java.lang.Enum<com.seaboat.Labels0> {
  public static final com.seaboat.Labels0 ENVIRONMENT;
  public static final com.seaboat.Labels0 TRAFFIC;
  public static final com.seaboat.Labels0 PHONE;
  static {};
  public java.lang.String getName();
  public static com.seaboat.Labels0[] values();
  public static com.seaboat.Labels0 valueOf(java.lang.String);
}
複製代碼

能夠清晰地看到枚舉被編譯後其實就是一個類,該類被聲明成 final,說明其不能被繼承,同時它繼承了 Enum 類。枚舉裏面的元素被聲明成 static final ,另外生成一個靜態代碼塊 static{},最後還會生成 values 和 valueOf 兩個方法。下面以最簡單的 Labels 爲例,一個一個模塊來看。機器學習

Enum 類

Enum 類是一個抽象類,主要有 name 和 ordinal 兩個屬性,分別用於表示枚舉元素的名稱和枚舉元素的位置索引,而構造函數傳入的兩個變量恰好與之對應。分佈式

  • toString 方法直接返回 name。
  • equals 方法直接用 == 比較兩個對象。
  • hashCode 方法調用的是父類的 hashCode 方法。
  • 枚舉不支持 clone、finalize 和 readObject 方法。
  • compareTo 方法能夠看到就是比較 ordinal 的大小。
  • valueOf 方法,根據傳入的字符串 name 來返回對應的枚舉元素。
public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
        
    private final String name;
    
    private final int ordinal;

    public final String name() {
        return name;
    }

    public final int ordinal() {
        return ordinal;
    }

    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    
    public String toString() {
        return name;
    }

    public final boolean equals(Object other) {
        return this==other;
    }
    
    public final int hashCode() {
        return super.hashCode();
    }

    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && 
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    @SuppressWarnings("deprecation")
    protected final void finalize() { }

    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }
}
複製代碼

靜態代碼塊

能夠看到靜態代碼塊主要完成的工做就是先分別建立 Labels 對象,而後將「ENVIRONMENT」、「TRAFFIC」和「PHONE」字符串做爲 name ,按照順序分別分配位置索引0、一、2做爲 ordinal,而後將其值設置給建立的三個 Labels 對象的 name 和 ordinal 屬性,此外還會建立一個大小爲3的 Labels 數組 ENUM$VALUES,將前面建立出來的 Labels 對象分別賦值給數組。函數

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=4, locals=0, args_size=0
         0: new           #1 // class com/seaboat/Labels
         3: dup
         4: ldc           #14 // String ENVIRONMENT
         6: iconst_0
         7: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
        10: putstatic     #19 // Field ENVIRONMENT:Lcom/seaboat/Labels;
        13: new           #1 // class com/seaboat/Labels
        16: dup
        17: ldc           #21 // String TRAFFIC
        19: iconst_1
        20: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
        23: putstatic     #22 // Field TRAFFIC:Lcom/seaboat/Labels;
        26: new           #1 // class com/seaboat/Labels
        29: dup
        30: ldc           #24 // String PHONE
        32: iconst_2
        33: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
        36: putstatic     #25 // Field PHONE:Lcom/seaboat/Labels;
        39: iconst_3
        40: anewarray     #1 // class com/seaboat/Labels
        43: dup
        44: iconst_0
        45: getstatic     #19 // Field ENVIRONMENT:Lcom/seaboat/Labels;
        48: aastore
        49: dup
        50: iconst_1
        51: getstatic     #22 // Field TRAFFIC:Lcom/seaboat/Labels;
        54: aastore
        55: dup
        56: iconst_2
        57: getstatic     #25 // Field PHONE:Lcom/seaboat/Labels;
        60: aastore
        61: putstatic     #27 // Field ENUM$VALUES:[Lcom/seaboat/Labels;
        64: return
      LineNumberTable:
        line 5: 0
        line 3: 39
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
複製代碼

values 方法

能夠看到它是一個靜態方法,主要是使用了前面靜態代碼塊中的 Labels 數組 ENUM$VALUES,調用 System.arraycopy 對其進行復制,而後返回該數組。因此經過 Labels.values()[2]就能獲取到數組中索引爲2的元素。學習

public static com.seaboat.Labels[] values();
    descriptor: ()[Lcom/seaboat/Labels;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=5, locals=3, args_size=0
         0: getstatic     #27 // Field ENUM$VALUES:[Lcom/seaboat/Labels;
         3: dup
         4: astore_0
         5: iconst_0
         6: aload_0
         7: arraylength
         8: dup
         9: istore_1
        10: anewarray     #1 // class com/seaboat/Labels
        13: dup
        14: astore_2
        15: iconst_0
        16: iload_1
        17: invokestatic  #35 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
        20: aload_2
        21: areturn
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
複製代碼

valueOf 方法

該方法一樣是個靜態方法,能夠看到該方法的實現是間接調用了父類 Enum 類的 valueOf 方法,根據傳入的字符串 name 來返回對應的枚舉元素,好比能夠經過 Labels.valueOf("ENVIRONMENT")獲取 Labels.ENVIRONMENTui

public static com.seaboat.Labels valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)Lcom/seaboat/Labels;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #1 // class com/seaboat/Labels
         2: aload_0
         3: invokestatic  #43 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #1 // class com/seaboat/Labels
         9: areturn
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
複製代碼

總結

枚舉本質其實也是一個類,並且都會繼承java.lang.Enum類,同時還會生成一個靜態代碼塊 static{},而且還會生成 values 和 valueOf 兩個方法。而上述的工做都須要由編譯器來完成,而後咱們就能夠像使用咱們熟悉的類那樣去使用枚舉了。this

-------------推薦閱讀------------

2017文章彙總——機器學習篇

2017文章彙總——Java及中間件

2017文章彙總——深度學習篇

2017文章彙總——JDK源碼篇

------------------廣告時間----------------

公衆號的菜單已分爲「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。

鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠購買。感謝各位朋友。

爲何寫《Tomcat內核設計剖析》

歡迎關注:

這裏寫圖片描述
相關文章
相關標籤/搜索