1. 程式人生 > >深入java 讀讀列舉類Enum原始碼(轉載)

深入java 讀讀列舉類Enum原始碼(轉載)

文章分成4部分:

  • 定義
  • 成員變數
  • 建構函式
  • 其他方法

Enum

Enum類是java.lang包中一個類,他是Java語言中所有列舉型別的公共基類。

定義

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

1.抽象類。

首先,抽象類不能被例項化,所以我們在java程式中不能使用new關鍵字來宣告一個Enum,如果想要定義可以使用這樣的語法:

enum enumName{
    value1,value2
    method1(){}
    method2(){}
}

其次,看到抽象類,第一印象是肯定有類繼承他。至少我們應該是可以繼承他的,所以:

/**
 * @author hollis
 */
public class testEnum extends Enum{
}
public class testEnum extends Enum<Enum<E>>{
}
public class testEnum<E> extends Enum<Enum<E>>{
}

嘗試了以上三種方式之後,得出以下結論:Enum類無法被繼承

為什麼一個抽象類不讓繼承?enum定義的列舉是怎麼來的?難道不是對Enum的一種繼承嗎?帶著這些疑問我們來反編譯以下程式碼:

  enum Color {RED, BLUE, GREEN}

編譯器將會把他轉成如下內容:

/**
 * @author hollis
 */
public final class Color extends Enum<Color> {
  public static final Color[] values() { return (Color[])$VALUES.clone(); }
  public static Color valueOf(String name) { ... }

  private Color(String s, int i) { super(s, i); }

  public static final Color RED;
  public static final Color BLUE;
  public static final Color GREEN;

  private static final Color $VALUES[];

  static {
    RED = new Color("RED", 0);
    BLUE = new Color("BLUE", 1);
    GREEN = new Color("GREEN", 2);
    $VALUES = (new Color[] { RED, BLUE, GREEN });
  }
} 

短短的一行程式碼,被編譯器處理過之後竟然變得這麼多,看來,enmu關鍵字是java提供給我們的一個語法糖啊。。。從反編譯之後的程式碼中,我們發現,編譯器不讓我們繼承Enum,但是當我們使用enum關鍵字定義一個列舉的時候,他會幫我們在編譯後預設繼承java.lang.Enum類,而不像其他的類一樣預設繼承Object類。且採用enum聲明後,該類會被編譯器加上final宣告,故該類是無法繼承的。 PS:由於JVM類初始化是執行緒安全的,所以可以採用列舉類實現一個執行緒安全的單例模式。

2.實現ComparableSerializable介面。

Enum實現了Serializable介面,可以序列化。 Enum實現了Comparable介面,可以進行比較,預設情況下,只有同類型的enum才進行比較(原因見後文),要實現不同型別的enum之間的比較,只能複寫compareTo方法。

3.泛型:**<E extends Enum<E>>**

怎麼理解<E extends Enum<E>>

首先,這樣寫只是為了讓Java的API更有彈性,他主要是限定形態引數例項化的物件,故要求只能是Enum,這樣才能對 compareTo 之類的方法所傳入的引數進行形態檢查。所以,我們完全可以不必去關心他為什麼這麼設計。

好啦,我們回到這個令人實在是無法理解的<E extends Enum<E>>

首先我們先來“翻譯”一下這個Enum<E extends Enum<E>>到底什麼意思,然後再來解釋為什麼Java要這麼用。 我們先看一個比較常見的泛型:List<String>。這個泛型的意思是,List中存的都是String型別,告訴編譯器要接受String型別,並且從List中取出內容的時候也自動幫我們轉成String型別。 所以Enum<E extends Enum<E>>可以暫時理解為Enum裡面的內容都是E extends Enum<E>型別。 這裡的E我們就理解為列舉,extends表示上界,比如: List<? extends Object>,List中的內容可以是Object或者擴充套件自Object的類。這就是extends的含義。 所以,E extends Enum<E>表示為一個繼承了Enum<E>型別的列舉型別。 那麼,Enum<E extends Enum<E>>就不難理解了,就是一個Enum只接受一個Enum或者他的子類作為引數。相當於把一個子類或者自己當成引數,傳入到自身,引起一些特別的語法效果。

為什麼Java要這樣定義Enum

首先我們來科普一下enum,

/**
 * @author hollis
 */
enum Color{
    RED,GREEN,YELLOW
}
enum Season{
    SPRING,SUMMER,WINTER
}
public class EnumTest{
    public static void main(String[] args) {
        System.out.println(Color.RED.ordinal());
        System.out.println(Season.SPRING.ordinal());
    }
}

程式碼中兩處輸出內容都是 0 ,因為列舉型別的預設的序號都是從零開始的。

要理解這個問題,首先我們來看一個Enum類中的方法(暫時忽略其他成員變數和方法):

/**
 * @author hollis
 */
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
        private final int ordinal;

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

首先我們認為Enum的定義中沒有使用Enum

public final int compareTo(Object o) 

當我們呼叫compareTo方法的時候依然傳入兩個列舉型別,在compareTo方法的實現中,比較兩個列舉的過程是先將引數轉化成Enum型別,然後再比較他們的序號是否相等。那麼我們這樣比較:

Color.RED.compareTo(Color.RED);
Color.RED.compareTo(Season.SPRING);

如果在compareTo方法中不做任何處理的話,那麼以上這段程式碼返回內容將都是true(因為Season.SPRING的序號和Color.RED的序號都是 0 )。但是,很明顯, Color.RED和Season.SPRING並不相等。

但是Java使用Enum

The method compareTo(Color) in the type Enum<Color> is not applicable for the arguments (Season)

他說明,compareTo方法只接受Enum型別。

Java為了限定形態引數例項化的物件,故要求只能是Enum,這樣才能對 compareTo之類的方法所傳入的引數進行形態檢查。 因為“紅色”只有和“綠色”比較才有意義,用“紅色”和“春天”比較毫無意義,所以,Java用這種方式一勞永逸的保證像compareTo這樣的方法可以正常的使用而不用考慮型別。

       PS:在Java中,其實也可以實現“紅色”和“春天”比較,因為Enum實現了Comparable介面,可以重寫compareTo方法來實現不同的enum之間的比較。

成員變數

在Enum中,有兩個成員變數,一個是名字(name),一個是序號(ordinal)。 序號是一個列舉常量,表示在列舉中的位置,從0開始,依次遞增。

/**
 * @author hollis
 */
private final String name;
public final String name() {
    return name;
}
private final int ordinal;
public final int ordinal() {
    return ordinal;
}

建構函式

前面我們說過,Enum是一個抽象類,不能被例項化,但是他也有建構函式,從前面我們反編譯出來的程式碼中,我們也發現了Enum的建構函式,在Enum中只有一個保護型別的建構函式:

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

文章開頭反編譯的程式碼中private Color(String s, int i) { super(s, i); }中的super(s, i);就是呼叫Enum中的這個保護型別的建構函式來初始化name和ordinal。

其他方法

Enum當中有以下這麼幾個常用方法,呼叫方式就是使用Color.RED.methodName(params...)的方式呼叫

public String toString() {
    return name;
}

public final boolean equals(Object other) {
    return this==other;
}

public final int hashCode() {
    return super.hashCode();
}

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

public final Class<E> getDeclaringClass() {
    Class clazz = getClass();
    Class zuper = clazz.getSuperclass();
    return (zuper == Enum.class) ? clazz : 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);
}

方法內容都比較簡單,平時能使用的就會也不是很多,這裡就不詳細介紹了。

參考資料: