深入理解 Java 枚舉 Enum 類型用法

枚舉(enum),是指一個(gè)經(jīng)過排序的、被打包成一個(gè)單一實(shí)體的項(xiàng)列表。一個(gè)枚舉的實(shí)例可以使用枚舉項(xiàng)列表中任意單一項(xiàng)的值。枚舉在各個(gè)語言當(dāng)中都有著廣泛的應(yīng)用,通常用來表示諸如顏色、方式、類別、狀態(tài)等等數(shù)目有限、形式離散、表達(dá)又極為明確的量。Java從JDK5開始,引入了對(duì)枚舉的支持

在枚舉出現(xiàn)之前,如果想要表示一組特定的離散值,往往使用一些常量。例如:

package com.fhp.enumexample;
 
public class Entity {
    
    public static final int VIDEO = 1;//視頻
    public static final int AUDIO = 2;//音頻
    public static final int TEXT = 3;//文字
    public static final int IMAGE = 4;//圖片
    
    private int id;
    private int type;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }   
}

當(dāng)然,常量也不僅僅局限于int型,諸如char和String等也是不在少數(shù)。然而,無論使用什么樣的類型,這樣做都有很多的壞處。這些常量通常都是連續(xù)、有無窮多個(gè)值的量,而類似這種表示類別的量則是離散的,并且通常情況下只有有限個(gè)值。用連續(xù)的量去表示離散量,會(huì)產(chǎn)生很多問題。例如,針對(duì)上述的Entity類,如果要對(duì)Entity對(duì)象的type屬性進(jìn)行賦值,一般會(huì)采用如下方法:

Entity e = new Entity();
e.setId(10);
e.setType(2);

這樣做的缺點(diǎn)有:(1)代碼可讀性差、易用性低。由于setType()方法的參數(shù)是int型的,在閱讀代碼的時(shí)候往往會(huì)讓讀者感到一頭霧水,根本不明白這個(gè)2到底是什么意思,代表的是什么類型。當(dāng)然,要保證可讀性,還有這樣一個(gè)辦法:

e.setType(Entity.AUDIO);

而這樣的話,問題又來了。這樣做,客戶端必須對(duì)這些常量去建立理解,才能了解如何去使用這個(gè)東西。說白了,在調(diào)用的時(shí)候,如果用戶不到Entity類中去看看,還真不知道這個(gè)參數(shù)應(yīng)該怎么傳、怎么調(diào)。像是setType(2)這種用法也是在所難免,因?yàn)樗耆戏ǎ皇敲總€(gè)人都能夠建立起用常量名代替數(shù)值,從而增加程序可讀性、降低耦合性的意識(shí)

(2)類型不安全。在用戶去調(diào)用的時(shí)候,必須保證類型完全一致,同時(shí)取值范圍也要正確。像是setType(-1)這樣的調(diào)用是合法的,但它并不合理,今后會(huì)為程序帶來種種問題。也許你會(huì)說,加一個(gè)有效性驗(yàn)證嘛,但是,這樣做的話,又會(huì)引出下面的第(3)個(gè)問題

(3)耦合性高,擴(kuò)展性差。假如,因?yàn)槟承┰?,需要修改Entity類中常量的值,那么,所有用到這些常量的代碼也就都需要修改——當(dāng)然,要仔細(xì)地修改,萬一漏了一個(gè),那可不是開玩笑的。同時(shí),這樣做也不利于擴(kuò)展。例如,假如針對(duì)類別做了一個(gè)有效性驗(yàn)證,如果類別增加了或者有所變動(dòng),則有效性驗(yàn)證也需要做對(duì)應(yīng)的修改,不利于后期維護(hù)

(4)常量作為參數(shù)時(shí),是String,int等弱類型,開發(fā)人員可以傳入沒有在常量接口里定義的值,這個(gè)問題無法通過編譯器發(fā)現(xiàn)

(5)編譯時(shí),是直接把常量的值編譯到類的二進(jìn)制代碼里,常量的值在升級(jí)中變化后,需要重新編譯引用常量的類,因?yàn)槔锩娲娴氖桥f值

(6)如果常量類的構(gòu)造器不私有,無法限制開發(fā)員繼承/實(shí)現(xiàn)接口,開發(fā)員能夠在子接口里繼續(xù)添加常量.而這些常量可能得不到祖先層的支持

枚舉就是為了這樣的問題而誕生的。它們給出了將一個(gè)任意項(xiàng)同另一個(gè)項(xiàng)相比較的能力,并且可以在一個(gè)已定義項(xiàng)列表中進(jìn)行迭代。枚舉(在Jave中簡稱為enum)是一個(gè)特定類型的類。所有枚舉都是Java中的新類java.lang.Enum的隱式子類。此類不能手工進(jìn)行子類定義。一個(gè)簡單的枚舉可以是這樣:

package com.fhp.enumexample;
 
public enum TypeEnum {
    VIDEO, AUDIO, TEXT, IMAGE
}

上面的Entity類就可以改成這樣:

package com.fhp.enumexample;
 
public class Entity {
    
    private int id;
    private TypeEnum type;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
        
    public TypeEnum getType() {
        return type;
    }
    public void setType(TypeEnum type) {
        this.type = type;
    }
}

在為Entity對(duì)象賦值的時(shí)候,就可以這樣:

Entity e = new Entity();
e.setId(10);
e.setType(TypeEnum.AUDIO);

怎么看,都是好了很多。在調(diào)用setType()時(shí),可選值只有四個(gè),否則會(huì)出現(xiàn)編譯錯(cuò)誤,因此可以看出,枚舉是類型安全的,不會(huì)出現(xiàn)取值范圍錯(cuò)誤的問題。同時(shí),客戶端不需要建立對(duì)枚舉中常量值的了解,使用起來很方便,并且可以容易地對(duì)枚舉進(jìn)行修改,而無需修改客戶端。如果常量從枚舉中被刪除了,那么客戶端將會(huì)失敗并且將會(huì)收到一個(gè)錯(cuò)誤消息。枚舉中的常量名稱可以被打印,因此除了僅僅得到列表中項(xiàng)的序號(hào)外還可以獲取更多信息。這也意味著常量可用作集合的名稱,例如HashMap

基本enum特征

所有創(chuàng)建的枚舉類都繼承自抽象類 java.lang.Enum;

一個(gè)枚舉類,所有實(shí)例都要在第一句寫出以 ,隔開。 如果只有實(shí)例最后可以不加 ; 枚舉類因?yàn)槔^承了Enum,所以再不能繼承別的類,任何類也不能繼承枚舉類(構(gòu)造器默認(rèn)為private)

public enum Color {
    RED,
    BLUE,
    YELLOW,
    PURPLE
}

注意 :RED,BLUE 這些是由 enum Color類調(diào)用默認(rèn)private構(gòu)造器創(chuàng)建的實(shí)例對(duì)象,和普通class相比只不過enum的實(shí)例對(duì)象只能在內(nèi)部創(chuàng)建。時(shí)刻記著他們是一個(gè)實(shí)例對(duì)象

枚舉類實(shí)例不能在外部 new 出來 ,因?yàn)槊杜e類構(gòu)造器為private 一般也不用聲明private,默認(rèn)就是private,因?yàn)閑num實(shí)例只能在編譯期間enum類內(nèi)部被創(chuàng)建,但可以外部得到一個(gè)實(shí)例的引用

Color red  = Color.RED;
Color blue = Color.BLUE;

枚舉類的一些方法

  1. 使用枚舉元素 Color.RED 或者 red
  2. Color.values( ) 返回一個(gè)枚舉類數(shù)組,數(shù)組元素就是枚舉的實(shí)例。
  3. red.ordinal() 方法返回該實(shí)例聲明的次序從0開始。
  4. 2個(gè)實(shí)例可用 == 比較
  5. Enum 實(shí)現(xiàn)了 Comparable<E>和 Serializable 可以使用comparableTo( )方法,可以序列化
  6. a. getDeclaringClass() 返回 Color.class 對(duì)象
  7. a.name和a.toString( )方法一樣。
  8. ? valuesOf(string ) 返回一個(gè)實(shí)例對(duì)象 Color b = Color.valueOf("BLUE");
  9. 根據(jù)class對(duì)象返回實(shí)例 Color b = Color.valueOf( Color.class, "BLUE" )

通過帶參構(gòu)造器為枚舉實(shí)例添加描述信息。調(diào)用 getDes()就可以的到當(dāng)前對(duì)象的描述信息

調(diào)用 getDes( )方法就可以的到當(dāng)前對(duì)象的描述信息

public enum Color {
    RED("這是紅色"),
    BLUE("這是藍(lán)色"),
    YELLOW("這是黃色"),
    PURPLE("這是紫色");
    String des;
    Color( String s) {
        this.des = s;
    }
    public String getDes(){
        return des;
    }
}

重寫toString( )方法來為enum實(shí)例添加描述信息

public enum Color {
    RED,
    BLUE,
    YELLOW,
    PURPLE;

    @Override
    public String toString() {
        String id = name();
        return id + " is " + id.toLowerCase();
    }
}

通過name() 拿到當(dāng)前對(duì)象名字

enum的特殊方法

除了不能繼承enum類外,enum和其普通類沒區(qū)別,可以添加字段,方法,甚至是main 方法

  1. enum 實(shí)例也可以用在 switch語句中
  2. values()方法不在Enum中 它是由編譯器添加的static方法
  3. 編譯器還添加了一個(gè)valuesOf(String s)方法,這個(gè)只需要一個(gè)參數(shù)就可以的得到實(shí)例,而Enum的需要2個(gè)
  4. 如果將enum向上轉(zhuǎn)型為Enum那么values 和 valuesOf 無法再使用
  5. 與values 方法有相同作用的就是Class對(duì)象的getEnumConstants(),如果class是枚舉類那么返回元素?cái)?shù)組,不是枚舉類返回null

使用接口組織枚舉

為了實(shí)現(xiàn)擴(kuò)展enum 或者將enum分類,因?yàn)闊o法繼承所以靠擴(kuò)展子類無法實(shí)現(xiàn),可以利用接口來達(dá)到這些功能

public interface Food {
    enum Fruit implements Food{
        APPLE, BANANA, ORANGE;
    }
    enum Vegetables implements Food{
        TOMATO, POTATO, BEANS;
    }
    enum Drink implements Food{
        COFFEE, COCA, REDBULL;
    }
}


   public static void main(String[] args) {
        Food food = Food.Fruit.APPLE;
        food = Food.Drink.REDBULL;
        food = Food.Vegetables.BEANS;
    }

接口基礎(chǔ)上創(chuàng)建一個(gè)枚舉的枚舉,通過該enum控制其他enum,而不是不同的類型分別都要向上轉(zhuǎn)型為Food ,類多時(shí)分別向上轉(zhuǎn)型不如每個(gè)用一個(gè)enum控制方便

通過實(shí)例調(diào)用getValues方法就可以的到該實(shí)例的所有元素

public enum Course {
    FRUIT(Food.Fruit.class),
    DRINK(Food.Drink.class),
    VEGETABLES(Food.Vegetables.class);
    private Food[] values;
    Course(Class<? extends Food> kind) {
        this.values = kind.getEnumConstants();
    }
    public Food[] getValues() {
        return values;
    }
}

enum嵌套在另一個(gè)enum 重新組織1 2 代碼合二為一

public enum Course {
    FRUIT(Food.Fruit.class),
    DRINK(Food.Drink.class),
    VEGETABLES(Food.Vegetables.class);
    private Food[] values;

    Course(Class<? extends Food > kind) {
        this.values = kind.getEnumConstants();
    }

    interface Food {
        enum Fruit implements Food {
            APPLE, BANANA, ORANGE;
        }

        enum Vegetables implements Food {
            TOMATO, POTATO, BEANS;
        }

        enum Drink implements Food {
            COFFEE, COCA, REDBULL;
        }
    }

    public Food[] getValues() {
        return values;
    }
   
}

EnumSet

EnumSet (抽象類)一個(gè)用來存放enum 元素的Set,存取enum速度非???,性能非常高

  1. EnumSet 只能存放enum元素,不能插入空元素

  2. 放入的元素位置和enum中保持一樣,它處于排序狀態(tài),是一個(gè)有序Set

  3. Enum 是個(gè)抽象類且方法除了colon( )克隆一個(gè)EnumSet外都是靜態(tài)方法返回值都是EnumSet

EnumSet<Color> enumSet = EnumSet.noneOf(Color.class); //創(chuàng)建一個(gè)空集
EnumSet<Color> enumSet2 = EnumSet.allOf(Color.class); //把集合中所有元素添加進(jìn)去
EnumSet<Color> enumSet3 = EnumSet.of(RED);//添加一個(gè)元素

EnumSet不止這幾個(gè)方法,對(duì)于of() 方法重載了6次,當(dāng)傳入2-5個(gè)參數(shù)調(diào)用相應(yīng)方法,傳入1個(gè)或者5個(gè)以上調(diào)用可變參數(shù)

EnumMap

特殊Map(類),key必須是enum, 由于enum元素有限所以內(nèi)部只是由數(shù)組實(shí)現(xiàn)

  1. 這是一個(gè)有序map,保持enum的元素順序

  2. EnumMap<Color,Object> map = new EnumMap<Color, Object>(Color.class)

  3. 方法和其他Map一樣

實(shí)例對(duì)象添加方法

為每一個(gè)enum實(shí)例(相當(dāng)于常量)添加一個(gè)方法,讓他們有不同的行為

為每一個(gè)實(shí)例添加不同行為的方法

  1. 在enum中創(chuàng)建一個(gè)或者多個(gè)abstract 方法,因?yàn)槭莈num的實(shí)例所以就得實(shí)現(xiàn)這些方法
RED{
        @Override
        String getInfo() {
            return null;
        }

        @Override
        String getTime() {
            return null;
        }
    };
    abstract String getInfo();
    abstract String getTime();
  1. enum也可以有main方法作為enum執(zhí)行入口
  2. 常量添加方法后和有了類的行為,貌似和內(nèi)部類一樣,但他們有著不同行為,enum的常量不能作為方法參數(shù)類型,因?yàn)樗麄儾皇穷?,只是enum類型的static final 實(shí)例
  3. 由于enum的常量是 static final 的所以常量的方法不能訪問外部類的非靜態(tài)方法

覆蓋常量相關(guān)的方法

  1. enum中所有非抽象方法每個(gè)實(shí)例都可以調(diào)
  2. 如果不需要每個(gè)實(shí)例都實(shí)現(xiàn)抽象類,那么就可以不用定義抽象類,每個(gè)實(shí)例各自實(shí)現(xiàn)方法,實(shí)現(xiàn)的方法可以覆蓋enum中的方法

使用enum職責(zé)鏈

  • 多種不同的方式解決問題,然后把它們連接在一起,但一個(gè)請(qǐng)求到達(dá)時(shí)遍歷整個(gè)鏈,直到解決問題
  • enum非常適合作為解決某一個(gè)問題的職責(zé)鏈,請(qǐng)求到達(dá)時(shí)遍歷整個(gè)enum,直到解決問題
import java.util.EnumSet;
import java.util.Random;

public enum COR {
    SOLUTION_ONE{
        @Override
        boolean Solve(int i) {
            if (i == 1){
                System.out.println(name()+" 解決問題 " +i);
                return true;
            } return false;
        }
    },
    SOLUTION_TWO{
        @Override
        boolean Solve(int i) {
            if (i == 2){
                System.out.println(name()+" 解決問題 " +i);
                return true;
            }return false;
        }
    },
    SOLUTION_THREE{
        @Override
        boolean Solve(int i) {
            if (i == 3){
                System.out.println(name()+" 解決問題 " +i);
                return true;
            }return false;
        }
    },
    SOLUTION_FOUR{
        @Override
        boolean Solve(int i) {
            if (i == 4){
                System.out.println(name()+" 可以解決問題 " +i);
                return true;
            }return false;
        }
    };

    abstract boolean Solve(int i);

    public static void main(String[] args) {
        Random random = new Random();
        EnumSet<COR> cors = EnumSet.allOf(COR.class);
        for (int i = 0; i < 6; i++) {
            int id = random.nextInt(4)+1;
            for (COR cor :cors) {
                if (cor.Solve(id)){
                    System.out.println(" 解決問題 " +id);
                    break;
                }
            }
        }
    }

}

enum狀態(tài)機(jī)

  • 狀態(tài)機(jī)可以具有 有限個(gè) 狀態(tài),通常根據(jù)輸入,從一個(gè)狀態(tài)轉(zhuǎn)移到下一個(gè)狀態(tài),也可以有瞬時(shí)狀態(tài),一但任務(wù)結(jié)束就立刻離開瞬時(shí)狀態(tài)

多路分發(fā)

  • 多種類型交互時(shí)有時(shí)并不能確定所有類型,如: NUM.complete(NUM) , NUM 是所有數(shù)字類型的超類,a.complete(b) ,a b可能是同種類型也可能不是同一種類型
  • Java 動(dòng)態(tài)綁定只能處理一種類型,屬于單路分發(fā)(分派),動(dòng)態(tài)綁定能將complete綁定到分路a。只有方法調(diào)用才會(huì)執(zhí)行動(dòng)態(tài)綁定

可以為每一個(gè)分發(fā)實(shí)現(xiàn)自己的動(dòng)態(tài)綁定

public enum Outcome { WIN, LOSE, DRAW } ///:~  
  
  
interface Item {  
    Outcome compete(Item it);  
  
    Outcome eval(Paper p);  
  
    Outcome eval(Scissors s);  
  
    Outcome eval(Rock r);  
}  
  
class Paper implements Item {  
    public Outcome compete(Item it) {  
        return it.eval(this);  
    }  
  
    public Outcome eval(Paper p) {  
        return DRAW;  
    }  
  
    public Outcome eval(Scissors s) {  
        return WIN;  
    }  
  
    public Outcome eval(Rock r) {  
        return LOSE;  
    }  
  
    public String toString() {  
        return "Paper";  
    }  
}  
  
class Scissors implements Item {  
    public Outcome compete(Item it) {  
        return it.eval(this);  
    }  
  
    public Outcome eval(Paper p) {  
        return LOSE;  
    }  
  
    public Outcome eval(Scissors s) {  
        return DRAW;  
    }  
  
    public Outcome eval(Rock r) {  
        return WIN;  
    }  
  
    public String toString() {  
        return "Scissors";  
    }  
}  
  
class Rock implements Item {  
    public Outcome compete(Item it) {  
        return it.eval(this);  
    }  
      
    public Outcome eval(Paper p) {  
        return WIN;  
    }  
  
    public Outcome eval(Scissors s) {  
        return LOSE;  
    }  
  
    public Outcome eval(Rock r) {  
        return DRAW;  
    }  
  
    public String toString() {  
        return "Rock";  
    }  
}  
  
public class RoShamBo1 {  
    static final int SIZE = 20;  
    private static Random rand = new Random(47);  
  
    public static Item newItem() {  
        switch (rand.nextInt(3)) {  
        default:  
        case 0:  
            return new Scissors();  
        case 1:  
            return new Paper();  
        case 2:  
            return new Rock();  
        }  
    }  
  
    public static void match(Item a, Item b) {  
        System.out.println(a + " vs. " + b + ": " + a.compete(b));  
    }  
  
    public static void main(String[] args) {  
        for (int i = 0; i < SIZE; i++)  
            match(newItem(), newItem());  
    }  
}

使用enum實(shí)現(xiàn)多路分發(fā)

  1. enum的實(shí)例不能作為類型參數(shù),不可以重載方法
  2. 可以使用enum構(gòu)造器初始化每個(gè)enum實(shí)例,并以一組結(jié)果作為參數(shù)如 ENUM_A( vsA_DRAW, vsB_LOSE, vsC_WIN ) 在比較方法中使用switch 判斷 返回 結(jié)果
package enums;

import static enums.OutCome.*;

public enum RoSham {
    PAPER(DRAW, LOSE, WIN),
    SCISSORS(WIN, DRAW, LOSE),
    ROCK(LOSE, WIN, DRAW);

    private OutCome vPAPER, vSCISSORS, vROCK;

    RoSham(OutCome paper, OutCome scissors, OutCome rock) {
        this.vPAPER = paper;
        this.vSCISSORS = scissors;
        this.vROCK = rock;
    }

    public OutCome complete(RoSham it) {
        switch (it) {
            default:
            case PAPER:
                return vPAPER;
            case SCISSORS:
                return vSCISSORS;
            case ROCK:
                return vROCK;
        }
    }

    public static void main(String[] args) {
        System.out.println(PAPER.complete(ROCK));
    }
}

PAPER.complete()時(shí)把PAPER構(gòu)造器中的結(jié)果與 OutCome 變量綁定,根據(jù)對(duì)比的參數(shù)返回對(duì)比結(jié)果,因此實(shí)例構(gòu)造器中的參數(shù)位置非常重要

EnumMap實(shí)現(xiàn)真正的多路分發(fā)

package enums;

import java.util.EnumMap;

import static enums.OutCome.*;

public enum RoShamBo {
    PAPER, SCISSORS, ROCK;
    static EnumMap<RoShamBo, EnumMap<RoShamBo, OutCome>>
            table = new EnumMap<RoShamBo, EnumMap<RoShamBo, OutCome>>(RoShamBo.class);

    static {
        for (RoShamBo it : RoShamBo.values()) {

            table.put(it, new EnumMap<RoShamBo, OutCome>(RoShamBo.class));
        }

        initRow(PAPER, DRAW, LOSE, WIN);
        initRow(SCISSORS, WIN, DRAW, LOSE);
        initRow(ROCK, LOSE, WIN, DRAW);

    }

    static void initRow(RoShamBo it, OutCome vPAPER, OutCome vSCISSORS, OutCome vROCK) {
        EnumMap<RoShamBo, OutCome> row = RoShamBo.table.get(it);
        row.put(RoShamBo.PAPER, vPAPER);
        row.put(RoShamBo.SCISSORS, vSCISSORS);
        row.put(RoShamBo.ROCK, vROCK);
    }

    public OutCome complete(RoShamBo it) {
        return table.get(this).get(it);
    }

    public static void main(String[] args) {
        System.out.println(ROCK.complete(SCISSORS));
    }

}

complete方法實(shí)現(xiàn)了2次分發(fā)

使用二維數(shù)組

  • 簡單,速度快,代碼易懂,但是組數(shù)比較大時(shí)尺寸容易錯(cuò)
private static OutCome[][] tables = {
        {DRAW, LOSE, WIN},
        {WIN, DRAW, LOSE},
        {LOSE, WIN, DRAW},
    };

    public OutCome completes (RoShamBo other) {
        return tables[this.ordinal()][other.ordinal()];
    }

知識(shí)點(diǎn)

可以靜態(tài)導(dǎo)入枚舉類 直接使用枚舉實(shí)例 import static ......Color.* 最好使用靜態(tài)導(dǎo)入省去寫enum類

參考文章

《Java編程思想》 -- 筆記

《Java編程思想》筆記 第十九章 枚舉類型

淺談在Java開發(fā)中的枚舉的作用和用法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容