天天看點

Java 枚舉類型 enum

以下使用 JDK 版本為:1.8.0_121

枚舉類型的引入

枚舉類型是 Java 5 中增加的一個小特性,在此之前,實作枚舉的方式為定義編譯期常量形式。而代碼中使用編譯期常量的方式在了解性和安全性方面存在不足,單純的常量數值不能夠直覺的展現出其作為枚舉執行個體的目的,且實際中無限的取值空間也不能很好的與理論上有限的枚舉空間相比對。

常見例證為四季的代碼中表示方式

常量形式的表示為:

class Season{
    
    public static final int SPRING = 1;

    public static final int SUMMER = 2;

    public static final int AUTUMN = 3;

    public static final int WINTER = 4;
    
}
           

enum 枚舉類型的表示為:

enum Season{
    SPRING, SUMMER, AUTUMN, WINTER;
}
           

根據類型的定義可以看出兩者存在明顯的差異,枚舉類型的執行個體擺脫了數值的限制,能夠以執行個體自身表達其作用和目的。

枚舉類型的使用

enum 關鍵字與常用的 class 關鍵字作用相似,用于完成對類結構的定義,是以 enum 可以作為一種特殊的類定義方式了解。定義過程與 class 的定義類過程基本相同,同樣提供有屬性和方法的定義,不同之處在于 enum 定義的類預設繼承了 Enum 類,因為 Java 不允許多繼承,是以使用 enum 定義的類不能再繼承其它類。

使用示例:

enum Season {
    SPRING("first season"), SUMMER("second season"), AUTUMN("third season"), WINTER("last season");

    private String describe;

    Season(String describe) {
        this.describe = describe;
    }

    public String toString() {
        return describe;
    }
}

class Test {
    public static void main(String[] args) {
        Season spring = Season.SPRING;
        System.out.println(spring);
    }
}
           

輸出為:

first season
           

因為重新定義了 toString 方法,是以列印内容為枚舉執行個體的描述内容。

  • 執行個體屬性 name、ordinal,及同名方法

該示例中給 Season 枚舉類型增加了一個 describe 屬性,其實在 Enum 類中已經具有兩個屬性 name 和 ordinal,分别表示枚舉執行個體名稱和序号,并提供有同名函數,用于傳回執行個體屬性值。

name、ordinal 屬性列印示例:

class Test {
    public static void main(String[] args) {
        Season spring = Season.SPRING;
        System.out.println(spring);
        System.out.println(spring.name());
        System.out.println(spring.ordinal());
    }
}
           
first season
SPRING
0
           

觀察 Enum 源代碼中兩個屬性,及同名函數的定義:

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

    private final String name;

    public final String name() {
        return name;
    }

    private final int ordinal;

    public final int ordinal() {
        return ordinal;
    }

    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    ...  // omit some content
}
           

從源碼中可知 Enum 的構造函數為 protected 級别,觀察示例中 Season 枚舉類内部聲明的四個執行個體,發現并未顯式調用基類的構造函數,甚至連自身的構造函數也沒有顯式調用。由此可知,枚舉類内部定義的多個執行個體,其構造過程是由編譯器調用構造函數來處理完成的。

反編譯 Season 類定義檔案:

final class Season extends Enum
{

    public static Season[] values()
    {
        return (Season[])$VALUES.clone();
    }

    public static Season valueOf(String s)
    {
        return (Season)Enum.valueOf(t1/Season, s);
    }

    private Season(String s, int i, String s1)
    {
        super(s, i);
        describe = s1;
    }

    public String toString()
    {
        return describe;
    }

    public static final Season SPRING;
    public static final Season SUMMER;
    public static final Season AUTUMN;
    public static final Season WINTER;
    private String describe;
    private static final Season $VALUES[];

    static 
    {
        SPRING = new Season("SPRING", 0, "first season");
        SUMMER = new Season("SUMMER", 1, "second season");
        AUTUMN = new Season("AUTUMN", 2, "third season");
        WINTER = new Season("WINTER", 3, "last season");
        $VALUES = (new Season[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}
           

由反編譯内容可知,Season 内部聲明的多個執行個體,其構造過程是編譯器在靜态代碼庫中,調用 Season 自身和基類的構造函數完成定義的。

  • valueOf 與 values 函數

從上圖中的反編譯内容可以發現兩個額外的方法定義,其中 valueOf 函數在 Enum 源碼中已有定義,但是比較 Enum 源碼中的 valueOf 函數與此處反編譯生成的 valueOf 函數實作,可以發現,編譯器生成的 valueOf 函數内部調用的其實就是 Enum 類中定義的 valueOf 函數。

// compiler generation
    public static Season valueOf(String s) {
        return (Season)Enum.valueOf(t1 / Season, s);
    }

    // defined in the source code
    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);
    }
           

上圖中第一種為編譯器生成的 valueOf 函數體,第二種為 Enum 源碼中定義的函數體。觀察第二種實作,可以發現,其實調用的是 Class 類中定義的 enumConstantDirectory 函數,在該函數内對參數 enumType 是否為枚舉類型進行判斷,并傳回枚舉執行個體名稱和枚舉執行個體構成的 map 對象。

反編譯生成的 values 函數,則是傳回枚舉類内部聲明的執行個體數組的 clone 對象。

valueOf、values 函數示例:

class Test {
    public static void main(String[] args) {
        System.out.println(Arrays.asList(Season.values()));
        System.out.println(Season.valueOf("SUMMER"));
    }
}
           
[first season, second season, third season, last season]
second season
           

因為重寫了 toString 函數,是以輸出為 describe 屬性構成的數組,根據枚舉執行個體的 name 值,valueOf 函數從 map 集合中傳回枚舉執行個體。

  • compareTo 函數

因為 Enum 類實作了Comparable 接口,而 Comparable 接口中隻有一個函數聲明,也就是 compareTo 函數,是以枚舉執行個體之間可以進行比較。比較的結果是個整型數字,比較的内容是枚舉執行個體的聲明序号,比較的方式是聲明序号的內插補點,即 ordinal 屬性的內插補點。

compareTo 函數示例:

class Test {
    public static void main(String[] args) {
        Season summer = Season.valueOf("SUMMER");
        for(Season s : Season.values()){
            System.out.print(summer.name()+" compare to "+s.name());
            System.out.println(" result: "+summer.compareTo(s));
        }
    }
}
           
SUMMER compare to SPRING result: 1
SUMMER compare to SUMMER result: 0
SUMMER compare to AUTUMN result: -1
SUMMER compare to WINTER result: -2
           

傳回正數表示目前對象較大,0 表示大小相等,負數表示目前對象較小,內插補點表示聲明順序的差距大小。

  • 枚舉類型的 switch 用法

枚舉類型描述的是一個有限空間的元素集合,是以對元素的判斷應該是常見的操作。枚舉類型支援 switch 用法,是以避免了多個 if-else 判斷的形式。

switch 使用示例:

class Test {
    public static void main(String[] args) {
        Season summer = Season.valueOf("SUMMER");
        switch (summer){
            case SPRING:
                System.out.println(Season.SPRING);
                break;
            case SUMMER:
                System.out.println(Season.SUMMER);
                break;
            case AUTUMN:
                System.out.println(Season.AUTUMN);
                break;
            case WINTER:
                System.out.println(Season.WINTER);
                break;
            default:
                System.out.println("there is something wrong!");
        }
    }
}
           
second season
           
  • 枚舉類中實作接口函數

枚舉類型隐式繼承了 Enum 類,是以不能再繼承其他類,通過實作接口,可以将實作了同一個接口的多個枚舉類型作為同一類,進而實作對枚舉類型的劃分和歸類。由于枚舉類型的所有執行個體都在類内部定義完成,是以枚舉類實作接口的方式相較于普通類略有不同。

類内部實作接口函數

enum Season implements Rain{
    SPRING("first season"), SUMMER("second season"), AUTUMN("third season"), WINTER("last season");

    private String describe;

    Season(String describe) {
        this.describe = describe;
    }

    public String toString() {
        return describe;
    }

    public void rain(){  // implement interface method
        System.out.println(this+" is raining.");
    }
}

class Test {
    public static void main(String[] args) {
        Season summer = Season.valueOf("SUMMER");
        for(Season s : Season.values()){
            s.rain();
        }
    }
}
           
first season is raining.
second season is raining.
third season is raining.
last season is raining.
           

在枚舉類内部完成對接口函數的實作,所有枚舉類型都調用同樣的函數實作。

執行個體内部實作接口函數

enum Season implements Rain{
    SPRING("first season"){
        public void rain(){  // implement interface method
            System.out.println("first season is raining.");
        }
    }, SUMMER("second season"){
        public void rain(){  // implement interface method
            System.out.println("second season is raining.");
        }
    }, AUTUMN("third season"){
        public void rain(){  // implement interface method
            System.out.println("third season is raining.");
        }
    }, WINTER("last season"){
        public void rain(){  // implement interface method
            System.out.println("last season is raining.");
        }
    };

    private String describe;

    Season(String describe) {
        this.describe = describe;
    }

    public String toString() {
        return describe;
    }
    
}

class Test {
    public static void main(String[] args) {
        Season summer = Season.valueOf("SUMMER");
        for(Season s : Season.values()){
            s.rain();
        }
    }
}
           
first season is raining.
second season is raining.
third season is raining.
last season is raining.
           

因為枚舉類内部聲明定義了所有枚舉執行個體,是以當每個執行個體都實作了接口函數時,在類内部可以不實作接口函數,因為即便類内部實作了,在每個執行個體的構造時也重寫了該接口函數。是以可知,若并沒有給每個執行個體實作該接口函數,則必須在枚舉類内部實作該函數。

部分執行個體内部實作接口函數

enum Season implements Rain {
    SPRING("first season") ,
    SUMMER("second season") {
        public void rain() {  // implement interface method
            System.out.println("heavy rain.");
        }
    }, AUTUMN("third season"), WINTER("last season");

    private String describe;

    Season(String describe) {
        this.describe = describe;
    }

    public String toString() {
        return describe;
    }

    public void rain() {  // implement interface method
        System.out.println("patter raining.");
    }
}
           

被劃分為同一類的枚舉類型中,若某個執行個體在某些細節方面與其他執行個體較為不同,則可以單獨對其進行實作。

引用:
Java Enums Tutorial A Guide to Java Enums