枚舉(enum)讲岁,是指一個(gè)經(jīng)過(guò)排序的、被打包成一個(gè)單一實(shí)體的項(xiàng)列表阱洪。一個(gè)枚舉的實(shí)例可以使用枚舉項(xiàng)列表中任意單一項(xiàng)的值芬迄。枚舉在各個(gè)語(yǔ)言當(dāng)中都有著廣泛的應(yīng)用问顷,通常用來(lái)表示諸如顏色、方式禀梳、類別杜窄、狀態(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ù)胆胰。然而狞贱,無(wú)論使用什么樣的類型,這樣做都有很多的壞處蜀涨。這些常量通常都是連續(xù)瞎嬉、有無(wú)窮多個(gè)值的量,而類似這種表示類別的量則是離散的厚柳,并且通常情況下只有有限個(gè)值氧枣。用連續(xù)的量去表示離散量,會(huì)產(chǎn)生很多問(wè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);
而這樣的話,問(wèn)題又來(lái)了扇商。這樣做凤瘦,客戶端必須對(duì)這些常量去建立理解,才能了解如何去使用這個(gè)東西案铺。說(shuō)白了廷粒,在調(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ì)為程序帶來(lái)種種問(wèn)題吊宋。也許你會(huì)說(shuō),加一個(gè)有效性驗(yàn)證嘛颜武,但是璃搜,這樣做的話,又會(huì)引出下面的第(3)個(gè)問(wèn)題
(3)耦合性高鳞上,擴(kuò)展性差这吻。假如,因?yàn)槟承┰蚋菀椋枰薷腅ntity類中常量的值唾糯,那么,所有用到這些常量的代碼也就都需要修改——當(dāng)然,要仔細(xì)地修改移怯,萬(wàn)一漏了一個(gè)拒名,那可不是開玩笑的。同時(shí)芋酌,這樣做也不利于擴(kuò)展增显。例如,假如針對(duì)類別做了一個(gè)有效性驗(yàn)證脐帝,如果類別增加了或者有所變動(dòng)同云,則有效性驗(yàn)證也需要做對(duì)應(yīng)的修改,不利于后期維護(hù)
(4)常量作為參數(shù)時(shí)堵腹,是String炸站,int等弱類型,開發(fā)人員可以傳入沒有在常量接口里定義的值疚顷,這個(gè)問(wèn)題無(wú)法通過(guò)編譯器發(fā)現(xiàn)
(5)編譯時(shí)旱易,是直接把常量的值編譯到類的二進(jìn)制代碼里,常量的值在升級(jí)中變化后腿堤,需要重新編譯引用常量的類阀坏,因?yàn)槔锩娲娴氖桥f值
(6)如果常量類的構(gòu)造器不私有,無(wú)法限制開發(fā)員繼承/實(shí)現(xiàn)接口笆檀,開發(fā)員能夠在子接口里繼續(xù)添加常量.而這些常量可能得不到祖先層的支持
枚舉就是為了這樣的問(wèn)題而誕生的忌堂。它們給出了將一個(gè)任意項(xiàng)同另一個(gè)項(xiàng)相比較的能力,并且可以在一個(gè)已定義項(xiàng)列表中進(jìn)行迭代酗洒。枚舉(在Jave中簡(jiǎn)稱為enum)是一個(gè)特定類型的類士修。所有枚舉都是Java中的新類java.lang.Enum的隱式子類。此類不能手工進(jìn)行子類定義樱衷。一個(gè)簡(jiǎn)單的枚舉可以是這樣:
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ò)誤的問(wèn)題牲蜀。同時(shí),客戶端不需要建立對(duì)枚舉中常量值的了解绅这,使用起來(lái)很方便涣达,并且可以容易地對(duì)枚舉進(jìn)行修改,而無(wú)需修改客戶端。如果常量從枚舉中被刪除了度苔,那么客戶端將會(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相比只不過(guò)enum的實(shí)例對(duì)象只能在內(nèi)部創(chuàng)建。時(shí)刻記著他們是一個(gè)實(shí)例對(duì)象
枚舉類實(shí)例不能在外部 new 出來(lái) 福青,因?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;
枚舉類的一些方法
- 使用枚舉元素 Color.RED 或者 red
- Color.values( ) 返回一個(gè)枚舉類數(shù)組媒役,數(shù)組元素就是枚舉的實(shí)例。
- red.ordinal() 方法返回該實(shí)例聲明的次序從0開始宪迟。
- 2個(gè)實(shí)例可用 == 比較
- Enum 實(shí)現(xiàn)了 Comparable<E>和 Serializable 可以使用comparableTo( )方法刊愚,可以序列化
- a. getDeclaringClass() 返回 Color.class 對(duì)象
- a.name和a.toString( )方法一樣。
- ? valuesOf(string ) 返回一個(gè)實(shí)例對(duì)象 Color b = Color.valueOf("BLUE");
- 根據(jù)class對(duì)象返回實(shí)例 Color b = Color.valueOf( Color.class, "BLUE" )
通過(guò)帶參構(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( )方法來(lái)為enum實(shí)例添加描述信息
public enum Color {
RED,
BLUE,
YELLOW,
PURPLE;
@Override
public String toString() {
String id = name();
return id + " is " + id.toLowerCase();
}
}
通過(guò)name() 拿到當(dāng)前對(duì)象名字
enum的特殊方法
除了不能繼承enum類外鸥诽,enum和其普通類沒區(qū)別,可以添加字段箕憾,方法牡借,甚至是main 方法
- enum 實(shí)例也可以用在 switch語(yǔ)句中
- values()方法不在Enum中 它是由編譯器添加的static方法
- 編譯器還添加了一個(gè)valuesOf(String s)方法,這個(gè)只需要一個(gè)參數(shù)就可以的得到實(shí)例袭异,而Enum的需要2個(gè)
- 如果將enum向上轉(zhuǎn)型為Enum那么values 和 valuesOf 無(wú)法再使用
- 與values 方法有相同作用的就是Class對(duì)象的getEnumConstants()钠龙,如果class是枚舉類那么返回元素?cái)?shù)組,不是枚舉類返回null
使用接口組織枚舉
為了實(shí)現(xiàn)擴(kuò)展enum 或者將enum分類御铃,因?yàn)闊o(wú)法繼承所以靠擴(kuò)展子類無(wú)法實(shí)現(xiàn)碴里,可以利用接口來(lái)達(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è)枚舉的枚舉,通過(guò)該enum控制其他enum上真,而不是不同的類型分別都要向上轉(zhuǎn)型為Food 咬腋,類多時(shí)分別向上轉(zhuǎn)型不如每個(gè)用一個(gè)enum控制方便
通過(guò)實(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è)用來(lái)存放enum 元素的Set,存取enum速度非常快睡互,性能非常高
EnumSet 只能存放enum元素根竿,不能插入空元素
放入的元素位置和enum中保持一樣陵像,它處于排序狀態(tài),是一個(gè)有序Set
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)
這是一個(gè)有序map泞歉,保持enum的元素順序
EnumMap<Color,Object> map = new EnumMap<Color, Object>(Color.class)
方法和其他Map一樣
實(shí)例對(duì)象添加方法
為每一個(gè)enum實(shí)例(相當(dāng)于常量)添加一個(gè)方法,讓他們有不同的行為
為每一個(gè)實(shí)例添加不同行為的方法
- 在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();
- enum也可以有main方法作為enum執(zhí)行入口
- 常量添加方法后和有了類的行為腰耙,貌似和內(nèi)部類一樣,但他們有著不同行為撒汉,enum的常量不能作為方法參數(shù)類型沟优,因?yàn)樗麄儾皇穷悾皇莈num類型的static final 實(shí)例
- 由于enum的常量是 static final 的所以常量的方法不能訪問(wèn)外部類的非靜態(tài)方法
覆蓋常量相關(guān)的方法
- enum中所有非抽象方法每個(gè)實(shí)例都可以調(diào)
- 如果不需要每個(gè)實(shí)例都實(shí)現(xiàn)抽象類睬辐,那么就可以不用定義抽象類挠阁,每個(gè)實(shí)例各自實(shí)現(xiàn)方法,實(shí)現(xiàn)的方法可以覆蓋enum中的方法
使用enum職責(zé)鏈
- 多種不同的方式解決問(wèn)題溯饵,然后把它們連接在一起侵俗,但一個(gè)請(qǐng)求到達(dá)時(shí)遍歷整個(gè)鏈,直到解決問(wèn)題
- enum非常適合作為解決某一個(gè)問(wèn)題的職責(zé)鏈丰刊,請(qǐng)求到達(dá)時(shí)遍歷整個(gè)enum隘谣,直到解決問(wèn)題
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()+" 解決問(wèn)題 " +i);
return true;
} return false;
}
},
SOLUTION_TWO{
@Override
boolean Solve(int i) {
if (i == 2){
System.out.println(name()+" 解決問(wèn)題 " +i);
return true;
}return false;
}
},
SOLUTION_THREE{
@Override
boolean Solve(int i) {
if (i == 3){
System.out.println(name()+" 解決問(wèn)題 " +i);
return true;
}return false;
}
},
SOLUTION_FOUR{
@Override
boolean Solve(int i) {
if (i == 4){
System.out.println(name()+" 可以解決問(wèn)題 " +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(" 解決問(wèn)題 " +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ā)
- enum的實(shí)例不能作為類型參數(shù),不可以重載方法
- 可以使用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ù)組
- 簡(jiǎn)單选酗,速度快阵难,代碼易懂,但是組數(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編程思想》 -- 筆記