Java枚舉的使用方法詳解
Java枚舉的使用方法詳解
前言 你代碼中的flag和status,都應(yīng)該用枚舉來(lái)替代
很多人都說(shuō),枚舉在實(shí)際開(kāi)發(fā)中很少用到,甚至就沒(méi)用到。因?yàn)?,他們的代碼往往是這樣子的:
public class Constant {
/*
* 以下幾個(gè)變量表示英雄的狀態(tài)
*/
public final static int STATUS_WALKING = 0;//走
public final static int STATUS_RUNNINGING = 1;//跑
public final static int STATUS_ATTACKING = 2;//攻擊
public final static int STATUS_DEFENDING = 3;//防御
public final static int STATUS_DEAD = 4;//掛了
/*
* 以下幾個(gè)變量表示英雄的等級(jí)
*/
//此處略去N行代碼
}
然后,他們是這樣使用這個(gè)類(lèi)的:
hero.setStatus(Contant.STATUS_ATTACKING);
嗯,然后他們就說(shuō),“我在實(shí)際開(kāi)發(fā)中很少用到枚舉”
當(dāng)然,他們的意思是說(shuō)很少用到枚舉Enum這個(gè)類(lèi)。
但是,我想說(shuō)的是,上面這些代碼,通通應(yīng)該用Enum去實(shí)現(xiàn)。
為什么?
因?yàn)樗麄兊拇a完全建立在對(duì)隊(duì)友的信任,假設(shè)來(lái)了個(gè)奇葩隊(duì)友,做了這件事:
hero.setStatus(666);
你說(shuō),屏幕上的英雄會(huì)怎么樣呢?
總之,假如你在實(shí)際編程中經(jīng)常使用這樣的代碼,那是時(shí)候好好學(xué)習(xí)一下Enum了。
枚舉初探 為什么要使用枚舉類(lèi)型
生活中處處都有枚舉,包括“天然的枚舉”,比如行星、一周的天數(shù),也包括我們?cè)O(shè)計(jì)出來(lái)的枚舉,比如csdn的tab標(biāo)簽,菜單等。
Java代碼中表示枚舉的方式,大體上有兩種,一是int枚舉,而是Enum枚舉,當(dāng)然,我們都知道,Enum枚舉才是Java提供的真正枚舉。
那么,為什么我們要使用Enum枚舉類(lèi)型呢?先來(lái)看看在Java 1.5之前,沒(méi)有枚舉類(lèi)型時(shí),我們是怎樣表示枚舉的。
以八大行星為例,每個(gè)行星對(duì)應(yīng)一個(gè)int值,我們大概會(huì)這樣寫(xiě)
public class PlanetWithoutEnum {
public static final int PLANET_MERCURY = 0;
public static final int PLANET_VENUS = 1;
public static final int PLANET_EARTH = 2;
public static final int PLANET_MARS = 3;
public static final int PLANET_JUPITER = 4;
public static final int PLANET_SATURN = 5;
public static final int PLANET_URANUS = 6;
public static final int PLANET_NEPTUNE = 7;
}
這種叫int枚舉模式,當(dāng)然你也可以使用String枚舉模式,無(wú)論采用何種方式,這樣的做法,在類(lèi)型安全和使用方便性上都很差。
如果變量planet表示一個(gè)行星,使用者可以給這個(gè)值賦與一個(gè)不在我們枚舉值里面的值,比如 planet = 9,這是哪個(gè)行星估計(jì)也只有天知道了; 再者,我們很難計(jì)算出到底有多少個(gè)行星,我們也很難對(duì)行星進(jìn)行遍歷操作等等。
現(xiàn)在我們用枚舉來(lái)創(chuàng)建我們的行星。
public enum Planet {
MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE;
}
上面這個(gè)是最簡(jiǎn)單的枚舉,我們姑且叫做Planet 1.0,這個(gè)版本的行星枚舉,我們實(shí)現(xiàn)了一個(gè)功能,就是任何一個(gè)Planet類(lèi)型的變量,都可以由編譯器來(lái)保證,傳到給參數(shù)的任何非null對(duì)象一定屬于這八個(gè)行星之一。
然后,我們對(duì)Planet進(jìn)行升級(jí),Java允許我們給枚舉類(lèi)型添加任意的方法,這里引言書(shū)中的代碼,大家自行體會(huì)一下枚舉的構(gòu)造器、公共方法、枚舉遍歷等知識(shí)點(diǎn)。
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS(4.869e+24, 6.052e6),
EARTH(5.975e+24,6.378e6),
MARS(6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN(5.685e+26, 6.027e7),
URANUS(8.683e+25, 2.556e7),
NEPTUNE(1.024e+26,2.477e7);
private final double mass; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2
// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11;
// Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double mass() {
return mass;
}
public double radius() {
return radius;
}
public double surfaceGravity() {
return surfaceGravity;
}
public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}
//注:這里對(duì)書(shū)中的代碼做了微調(diào)
public class WeightTable {
public static void main(String[] args) {
printfWeightOnAllPlanets(8d);
}
public static void printfWeightOnAllPlanets(double earthWeight) {
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass));
}
}
運(yùn)行WeightTable,打印結(jié)果如下:
Weight on MERCURY is 3.023254 Weight on VENUS is 7.240408 Weight on EARTH is 8.000000 Weight on MARS is 3.036832 Weight on JUPITER is 20.237436 Weight on SATURN is 8.524113 Weight on URANUS is 7.238844 Weight on NEPTUNE is 9.090108
在這個(gè)小程序里,我們用到了枚舉的values()方法,這個(gè)方法返回了枚舉類(lèi)型里的枚舉變量的集合,非常實(shí)用。
枚舉進(jìn)階 計(jì)算器運(yùn)算符枚舉類(lèi)
上一小節(jié)的例子里,我們用到了枚舉類(lèi)的公共方法,這一節(jié),我們以計(jì)算器運(yùn)算符 Operation 枚舉類(lèi)為例,看看怎么實(shí)現(xiàn)對(duì)于
每一個(gè)枚舉對(duì)象,執(zhí)行不同的操作。
首先,我們很容易想到的一個(gè)方法,在公共方法里,使用switch去判斷枚舉類(lèi)型,然后執(zhí)行不同的操作,代碼如下:
public enum OperationUseSwitch {
PLUS, MINUS, TIMES, DIVIDE;
double apply(double x, double y) {
switch (this) {
case PLUS:
return x + y;
case MINUS:
return x + y;
case TIMES:
return x + y;
case DIVIDE:
return x + y;
}
// 如果this不屬于上面四種操作符,拋出異常
throw new AssertionError("Unknown operation: " + this);
}
}
這段代碼確實(shí)實(shí)現(xiàn)了我們的需求,但是有兩個(gè)弊端。
首先是我們不得不在最后拋出異?;蛘咴趕witch里加上default,不然無(wú)法編譯通過(guò),但是很明顯,程序的分支是不會(huì)進(jìn)入異常或者default的。
其次,這段代碼非常脆弱,如果我們添加了新的操作類(lèi)型,卻忘了在switch里添加相應(yīng)的處理邏輯,執(zhí)行新的運(yùn)算操作時(shí),就會(huì)出現(xiàn)問(wèn)題。
還好,Java枚舉提供了一種功能,叫做 特定于常量的方法實(shí)現(xiàn)。
我們只需要在枚舉類(lèi)型中聲明一個(gè)抽象方法,然后在各個(gè)枚舉常量中去覆蓋這個(gè)方法,實(shí)現(xiàn)如下:
public enum Operation {
PLUS {
double apply(double x, double y) {
return x + y;
}
},
MINUS {
double apply(double x, double y) {
return x - y;
}
},
TIMES {
double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
double apply(double x, double y) {
return x / y;
}
};
abstract double apply(double x, double y);
}
這樣,也就再也不會(huì)出現(xiàn)添加新操作符后忘記添加對(duì)應(yīng)的處理邏輯的情況了,因?yàn)榫幾g器就會(huì)提示我們必須覆蓋apply方法。
不過(guò),這種 特定于常量的方法實(shí)現(xiàn) 有一個(gè)缺點(diǎn),那就是你很難在枚舉常量之間共享代碼。
我們以星期X的枚舉為例,周一到周五是工作日,執(zhí)行一種邏輯,周六周日,休息日,執(zhí)行另一種邏輯。
如果還是使用 特定于常量的方法實(shí)現(xiàn),寫(xiě)出來(lái)的代碼可能就是這樣的:
public enum DayUseAbstractMethod {
MONDAY {
@Override
void apply() {
dealWithWeekDays();//偽代碼
}
},
TUESDAY {
@Override
void apply() {
dealWithWeekDays();//偽代碼
}
},
WEDNESDAY {
@Override
void apply() {
dealWithWeekDays();//偽代碼
}
},
THURSDAY {
@Override
void apply() {
dealWithWeekDays();//偽代碼
}
},
FRIDAY {
@Override
void apply() {
dealWithWeekDays();//偽代碼
}
},
SATURDAY {
@Override
void apply() {
dealWithWeekEnds();//偽代碼
}
},
SUNDAY {
@Override
void apply() {
dealWithWeekEnds();//偽代碼
}
};
abstract void apply();
}
很明顯,我們這段代碼里面有相當(dāng)多的重復(fù)代碼。
那么要怎么優(yōu)化呢,我們不妨這樣想,星期一星期二等等是一種枚舉,那么工作日和休息日,難道不也是一種枚舉嗎,我們能不能給Day的構(gòu)造函數(shù)傳入一個(gè)工作日休息日的DayType枚舉呢?這也就是書(shū)中給出的一種叫策略枚舉 的方法,代碼如下:
public enum Day {
MONDAY(DayType.WEEKDAY), TUESDAY(DayType.WEEKDAY), WEDNESDAY(
DayType.WEEKDAY), THURSDAY(DayType.WEEKDAY), FRIDAY(DayType.WEEKDAY), SATURDAY(
DayType.WEEKDAY), SUNDAY(DayType.WEEKDAY);
private final DayType dayType;
Day(DayType daytype) {
this.dayType = daytype;
}
void apply() {
dayType.apply();
}
private enum DayType {
WEEKDAY {
@Override
void apply() {
System.out.println("hi, weekday");
}
},
WEEKEND {
@Override
void apply() {
System.out.println("hi, weekend");
}
};
abstract void apply();
}
}
通過(guò)策略枚舉的方式,我們把Day的處理邏輯委托給了DayType,個(gè)中奧妙,讀者可以細(xì)細(xì)體會(huì)。
枚舉集合 EnumSet的使用
EnumSet提供了非常方便的方法來(lái)創(chuàng)建枚舉集合,下面這段代碼,感受一下
public class Text {
public enum Style {
BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
}
// Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) {
// Body goes here
for(Style style : styles){
System.out.println(style);
}
}
// Sample use
public static void main(String[] args) {
Text text = new Text();
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
}
}
這個(gè)例子里,我們使用了EnumSet.of方法,輕松創(chuàng)建了枚舉集合。
枚舉Map EnumMap的使用
假設(shè)對(duì)于香草(Herb),有一個(gè)枚舉屬性Type(一年生、多年生、兩年生)
Herb:
public class Herb {
public enum Type { ANNUAL, PERENNIAL, BIENNIAL }
private final String name;
private final Type type;
Herb(String name, Type type) {
this.name = name;
this.type = type;
}
@Override public String toString() {
return name;
}
}
現(xiàn)在,假設(shè)我們有一個(gè)Herb數(shù)組,我們需要對(duì)這個(gè)Herb數(shù)組按照Type進(jìn)行分類(lèi)存放。
所以接下來(lái),我們需要?jiǎng)?chuàng)建一個(gè)Map,value肯定是Herb的集合了,那么用什么作為key呢?
有的人會(huì)使用枚舉類(lèi)型的ordinal()方法,這個(gè)函數(shù)返回int類(lèi)型,表示枚舉遍歷在枚舉類(lèi)里的位置,這樣做,缺點(diǎn)很明顯,由于你的key的類(lèi)型是int,不能保證傳入的int一定能和枚舉類(lèi)里的變量對(duì)應(yīng)上。
所以,在key的選擇上,毫無(wú)疑問(wèn),只能使用枚舉類(lèi)型,也即Herb.Type。
最后還有一個(gè)問(wèn)題,要使用什么Map? Java為枚舉類(lèi)型專(zhuān)門(mén)提供了一種Map,叫EnumMap,相比較與其他Map,這種Map在處理枚舉類(lèi)型上更快,有興趣的同學(xué)可以研究一下這個(gè)map的內(nèi)部實(shí)現(xiàn)。
下面讓我們看看怎么使用EnumMap:
public static void main(String[] args) {
Herb[] garden = { new Herb("Basil", Type.ANNUAL),
new Herb("Carroway", Type.BIENNIAL),
new Herb("Dill", Type.ANNUAL),
new Herb("Lavendar", Type.PERENNIAL),
new Herb("Parsley", Type.BIENNIAL),
new Herb("Rosemary", Type.PERENNIAL) };
// Using an EnumMap to associate data with an enum - Page 162
Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(
Herb.Type.class);
for (Herb.Type t : Herb.Type.values())
herbsByType.put(t, new HashSet<Herb>());
for (Herb h : garden)
herbsByType.get(h.type).add(h);
System.out.println(herbsByType);
}
總結(jié)
和int枚舉相比,Enum枚舉的在類(lèi)型安全和使用便利上的優(yōu)勢(shì)是不言而喻的。
Enum為枚舉提供了豐富的功能,如文章中提到的特定于常量的方法實(shí)現(xiàn)和策略枚舉。
EnumSet和EnumMap是兩個(gè)為枚舉而設(shè)計(jì)的集合,在實(shí)際開(kāi)發(fā)中,用到枚舉集合時(shí),請(qǐng)優(yōu)先考慮這兩個(gè)。
如有疑問(wèn)請(qǐng)留言或者到本站社區(qū)交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Java簡(jiǎn)單實(shí)現(xiàn)UDP和TCP的示例
下面小編就為大家?guī)?lái)一篇Java簡(jiǎn)單實(shí)現(xiàn)UDP和TCP的示例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
MyBatisPlus批量添加的優(yōu)化與報(bào)錯(cuò)解決
MybatisPlus是一個(gè)高效的java持久層框架,它在Mybatis的基礎(chǔ)上增加了一些便捷的功能,提供了更加易用的API,可以大幅度提高開(kāi)發(fā)效率,這篇文章主要給大家介紹了關(guān)于MyBatisPlus批量添加的優(yōu)化與報(bào)錯(cuò)解決的相關(guān)資料,需要的朋友可以參考下2023-05-05
Java集合框架之LinkedHashSet類(lèi)解讀
這篇文章主要介紹了Java集合框架之LinkedHashSet類(lèi)解讀,LinkedHashSet是HashSet的有序版本,它跨所有元素維護(hù)一個(gè)雙向鏈接的List,當(dāng)需要維護(hù)迭代順序時(shí),就使用這個(gè)類(lèi),當(dāng)遍歷HashSet時(shí),順序是不可預(yù)測(cè)的,需要的朋友可以參考下2023-09-09
圖解Springboot集成七牛云并實(shí)現(xiàn)圖片上傳功能過(guò)程
在實(shí)際開(kāi)發(fā)中 ,基本都會(huì)有應(yīng)用到文件上傳的場(chǎng)景,但隨著或多或少的需求問(wèn)題,之前有在springboot上用過(guò)七牛云實(shí)現(xiàn)圖片上傳,今天因?yàn)槟承┰蛴种匦率褂昧讼缕吲T埔虼讼肟偨Y(jié)下七牛云2021-11-11
idea設(shè)置JVM運(yùn)行參數(shù)的幾種方式
對(duì)JVM運(yùn)行參數(shù)進(jìn)行修改是JVM性能調(diào)優(yōu)的重要手段,本文主要介紹了idea設(shè)置JVM運(yùn)行參數(shù)的幾種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04

