有的人說,不推薦使用枚舉。有的人說,枚舉很好用。究竟怎么使用,如何使用,仁者見仁智者見智??傊?,先學(xué)會(huì)再說~
為什么要引入枚舉類
一個(gè)小案例
你寫了一個(gè)小程序,不過好久不用了,突然有一天,你想使用一下它。程序要想正確運(yùn)行,需要將今天星期幾存到數(shù)據(jù)庫(kù)里。這個(gè)時(shí)候,你開始犯難了。
當(dāng)初的你還很年輕,不懂程序界的險(xiǎn)惡,設(shè)計(jì)這個(gè)程序的時(shí)候,傻不拉幾把這個(gè)字段設(shè)計(jì)為int類型的,用0代表周日,1代表周一。。。6代表周六,添加的時(shí)候就setWeekday(0)。但是這么長(zhǎng)時(shí)間沒用了,你忘記自己是從周一開始計(jì)算還是周日開始計(jì)算了,換句話說,你想不起來0代表的是周一還是周日了!
于是你各種翻代碼,看數(shù)據(jù)庫(kù)的字段,數(shù)據(jù)庫(kù)保存的信息,終于搞懂了,你很開心,用了一次之后,覺得這個(gè)程序沒意思,又不用了。
很久之后,你心血來潮,又想用一次它,很不幸,你又忘記到底0代表周一還是周日了,一番查找之后。你決定重構(gòu)代碼,因?yàn)槟闶懿涣肆耍。?/p>
靜態(tài)變量來幫忙
經(jīng)過一番思考,你決定使用七個(gè)靜態(tài)變量來代表星期幾,以后只要引用和靜態(tài)變量就可以了,而不用自己輸入012....你這么寫:
public class Weekday {
public final static int SUN = 0;
public final static int MON = 1;
public final static int TUE = 2;
public final static int WED = 3;
public final static int THU = 4;
public final static int FRI = 5;
public final static int SAT = 6;
}
機(jī)智如你,這個(gè)時(shí)候,只要Weekday.SUN就可以了,不用操心到底應(yīng)該填寫0還是填寫1。
但是這個(gè)時(shí)候的你,也不是當(dāng)初初出茅廬的小伙子了,很明顯,這樣寫已經(jīng)不能滿足你了。你還想讓這個(gè)類做更多的事,比如,你想知道下一天是星期幾,還想把今天是星期幾打印出來。一番深思熟慮后,你改成了這樣:
public class Weekday {
private Weekday(){}
public final static Weekday SUN = new Weekday();
public final static Weekday MON = new Weekday();
public final static Weekday TUE = new Weekday();
public final static Weekday WED = new Weekday();
public final static Weekday THU = new Weekday();
public final static Weekday FRI = new Weekday();
public final static Weekday SAT = new Weekday();
public static Weekday getNextDay(Weekday nowDay){
if(nowDay == SUN) {
return MON;
}else if(nowDay == MON) {
return TUE;
}else if(nowDay == TUE) {
return WED;
}else if(nowDay == WED) {
return THU;
}else if(nowDay == THU) {
return FRI;
}else if(nowDay == FRI) {
return SAT;
}else {
return SUN;
}
}
public static void printNowDay(Weekday nowDay){
if(nowDay == SUN)
System.out.println("sunday");
else if(nowDay == MON)
System.out.println("monday");
else if(nowDay == TUE)
System.out.println("tuesday");
else if(nowDay == WED)
System.out.println("wednesday");
else if(nowDay == THU)
System.out.println("thursday");
else if(nowDay == FRI)
System.out.println("friday");
else
System.out.println("saturday");
}
}
class Test1{
public static void main(String[] args) {
Weekday nowday = Weekday.SUN;
Weekday.printNowDay(nowday);
Weekday nextDay = Weekday.getNextDay(nowday);
System.out.print("nextday ====> ");
Weekday.printNowDay(nextDay);
}
}
//測(cè)試結(jié)果:
//sunday
//nextday ====> monday
喲,不錯(cuò)??紤]的很詳細(xì)。并且私有構(gòu)造方法后,外界就不能創(chuàng)建該類的對(duì)象了,這樣就避免了星期八星期九的出現(xiàn),所有Weekday的對(duì)象都在該類內(nèi)部創(chuàng)建。
不對(duì),好像缺了點(diǎn)什么,我要的是int!我的int呢?!。所以,你還需要一個(gè)這樣的方法:
public static int toInt(Weekday nowDay){
if(nowDay == SUN)
return 0;
else if(nowDay == MON)
return 1;
else if(nowDay == TUE)
return 2;
else if(nowDay == WED)
return 3;
else if(nowDay == THU)
return 4;
else if(nowDay == FRI)
return 5;
else
return 6;
}
當(dāng)你需要一個(gè)整形數(shù)據(jù)的時(shí)候,只需要Weekday.toInt(Weekday.SUN);,看起來你好像完成了你的任務(wù)。
但是,你有沒有發(fā)現(xiàn),這樣寫,好麻煩啊。如果想要擴(kuò)展一下功能,大量的ifelse會(huì)讓人眼花繚亂。
有沒有更好的方式呢?你大概已經(jīng)知道了,沒錯(cuò),我們需要枚舉類!
我們先來看看枚舉類是什么。
一個(gè)簡(jiǎn)單的枚舉類
話不多說,先來代碼:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
代碼這么少?
沒錯(cuò),這就是枚舉類,我們來看看怎么使用它:
class Test2{
public static void main(String[] args) {
Weekday sun = Weekday.SUN;
System.out.println(sun); // 輸出 SUN
}
}
看起來和上面的靜態(tài)變量使用方式差不多,而且默認(rèn)的toString方法返回的就是對(duì)應(yīng)的名字。
我們上面的那段代碼重寫toString也是不可以打印出當(dāng)前是星期幾的,因?yàn)閠oString方法沒有參數(shù)。所以我們自己寫了一個(gè)printNowDay方法。
當(dāng)然,這么簡(jiǎn)單的枚舉類是不可能實(shí)現(xiàn)我們的要求的,所以,我們還要接著寫:
public enum Weekday {
SUN(0),MON(1),TUS(2),WED(3),THU(4),FRI(5),SAT(6);
private int value;
private Weekday(int value){
this.value = value;
}
public static Weekday getNextDay(Weekday nowDay){
int nextDayValue = nowDay.value;
if (++nextDayValue == 7){
nextDayValue =0;
}
return getWeekdayByValue(nextDayValue);
}
public static Weekday getWeekdayByValue(int value) {
for (Weekday c : Weekday.values()) {
if (c.value == value) {
return c;
}
}
return null;
}
}
class Test2{
public static void main(String[] args) {
System.out.println("nowday ====> " + Weekday.SAT);
System.out.println("nowday int ====> " + Weekday.SAT.ordinal());
System.out.println("nextday ====> " + Weekday.getNextDay(Weekday.SAT)); // 輸出 SUN
//輸出:
//nowday ====> SAT
//nowday int ====> 6
//nextday ====> SUN
}
}
這樣就完成了我們的目標(biāo),和之前的代碼比起來,有沒有覺得突然高大上了許多?沒有那么多煩人的ifelse,世界都清凈了。
好了,現(xiàn)在你大概知道為什么要引入枚舉類了吧?就是因?yàn)樵跊]有枚舉類的時(shí)候,我們要定義一個(gè)有限的序列,比如星期幾,男人女人,春夏秋冬,一般會(huì)通過上面那種靜態(tài)變量的形式,但是使用那樣的形式如果需要一些其他的功能,需要些很多奇奇怪怪的代碼。所以,枚舉類的出現(xiàn),就是為了簡(jiǎn)化這種操作。
可以將枚舉類理解為是java的一種語法糖。
枚舉類的用法
最簡(jiǎn)單的使用
最簡(jiǎn)單的枚舉類就像我們上面第一個(gè)定義的枚舉類一樣:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
如何使用它呢?
先來看看它有哪些方法:
Weekday方法
這是Weekday可以調(diào)用的方法和參數(shù)。發(fā)現(xiàn)它有兩個(gè)方法:value()和valueOf()。還有我們剛剛定義的七個(gè)變量。
枚舉變量的方法
這些事枚舉變量的方法。我們接下來會(huì)演示幾個(gè)比較重要的:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
class Test3{
public static void main(String[] args) {
System.out.println(Weekday.valueOf("mon".toUpperCase()));
//MON
for (Weekday w : Weekday.values()){
System.out.println(w + ".ordinal() ====>" +w.ordinal());
}
//SUN.ordinal() ====>0
//MON.ordinal() ====>1
//TUS.ordinal() ====>2
//WED.ordinal() ====>3
//THU.ordinal() ====>4
//FRI.ordinal() ====>5
//SAT.ordinal() ====>6
System.out.println("Weekday.MON.compareTo(Weekday.FRI) ===> " + Weekday.MON.compareTo(Weekday.FRI));
System.out.println("Weekday.MON.compareTo(Weekday.MON) ===> " + Weekday.MON.compareTo(Weekday.MON));
System.out.println("Weekday.MON.compareTo(Weekday.SUM) ===> " + Weekday.MON.compareTo(Weekday.SUN));
//Weekday.MON.compareTo(Weekday.FRI) ===> -4
//Weekday.MON.compareTo(Weekday.MON) ===> 0
//Weekday.MON.compareTo(Weekday.SUM) ===> 1
System.out.println("Weekday.MON.name() ====> " + Weekday.MON.name());
//Weekday.MON.name() ====> MON
}
}
這段代碼,我們演示了幾個(gè)常用的方法和功能:
-
Weekday.valueOf() 方法:
它的作用是傳來一個(gè)字符串,然后將它轉(zhuǎn)變?yōu)閷?duì)應(yīng)的枚舉變量。前提是你傳的字符串和定義枚舉變量的字符串一抹一樣,區(qū)分大小寫。如果你傳了一個(gè)不存在的字符串,那么會(huì)拋出異常。
Weekday.values()方法。
這個(gè)方法會(huì)返回包括所有枚舉變量的數(shù)組。在該例中,返回的就是包含了七個(gè)星期的Weekday[]??梢苑奖愕挠脕碜鲅h(huán)。
-
枚舉變量的toString()方法。
該方法直接返回枚舉定義枚舉變量的字符串,比如MON就返回【"MON"】。
枚舉變量的.ordinal()方法。
默認(rèn)請(qǐng)款下,枚舉類會(huì)給所有的枚舉變量一個(gè)默認(rèn)的次序,該次序從0開始,類似于數(shù)組的下標(biāo)。而.ordinal()方法就是獲取這個(gè)次序(或者說下標(biāo))
- 枚舉變量的compareTo()方法。
該方法用來比較兩個(gè)枚舉變量的"大小",實(shí)際上比較的是兩個(gè)枚舉變量的次序,返回兩個(gè)次序相減后的結(jié)果,如果為負(fù)數(shù),就證明變量1"小于"變量2 (變量1.compareTo(變量2),返回【變量1.ordinal() - 變量2.ordinal()】)
compareTo源碼
這是compareTo的源碼,會(huì)先判斷是不是同一個(gè)枚舉類的變量,然后再返回差值。
- 枚舉類的name()方法。
它和toString()方法的返回值一樣,事實(shí)上,這兩個(gè)方法本來就是一樣的:
name方法
toString方法
這兩個(gè)方法的默認(rèn)實(shí)現(xiàn)是一樣的,唯一的區(qū)別是,你可以重寫toString方法。name變量就是枚舉變量的字符串形式。
還有一些其他的方法我就暫時(shí)不介紹了,感興趣的話可以自己去看看文檔或者源碼,都挺簡(jiǎn)單的。
要點(diǎn):
- 使用的是enum關(guān)鍵字而不是class。
- 多個(gè)枚舉變量直接用逗號(hào)隔開。
- 枚舉變量最好大寫,多個(gè)單詞之間使用"_"隔開(比如:INT_SUM)。
- 定義完所有的變量后,以分號(hào)結(jié)束,如果只有枚舉變量,而沒有自定義變量,分號(hào)可以省略(例如上面的代碼就忽略了分號(hào))。
- 在其他類中使用enum變量的時(shí)候,只需要【類名.變量名】就可以了,和使用靜態(tài)變量一樣。
但是這種簡(jiǎn)單的使用顯然不能體現(xiàn)出枚舉的強(qiáng)大,我們來學(xué)習(xí)一下復(fù)雜的使用:
枚舉的高級(jí)使用方法
就像我們前面的案例一樣,你需要讓每一個(gè)星期幾對(duì)應(yīng)到一個(gè)整數(shù),比如星期天對(duì)應(yīng)0。上面講到了,枚舉類在定義的時(shí)候會(huì)自動(dòng)為每個(gè)變量添加一個(gè)順序,從0開始。
假如你希望0代表星期天,1代表周一。。。并且你在定義枚舉類的時(shí)候,順序也是這個(gè)順序,那你可以不用定義新的變量,就像這樣:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
這個(gè)時(shí)候,星期天對(duì)應(yīng)的ordinal值就是0,周一對(duì)應(yīng)的就是1,滿足你的要求。但是,如果你這么寫,那就有問題了:
public enum Weekday {
MON,TUS,WED,THU,FRI,SAT,SUN
}
我吧SUN放到了最后,但是我還是希0代表SUN,1代表MON怎么辦呢?默認(rèn)的ordinal是指望不上了,因?yàn)樗粫?huì)傻傻的給第一個(gè)變量0,給第二個(gè)1。。。
所以,我們需要自己定義變量!
看代碼:
public enum Weekday {
MON(1),TUS(2),WED(3),THU(4),FRI(5),SAT(6),SUN(0);
private int value;
private Weekday(int value){
this.value = value;
}
}
我們對(duì)上面的代碼做了一些改變:
首先,我們?cè)诿總€(gè)枚舉變量的后面加上了一個(gè)括號(hào),里面是我們希望它代表的數(shù)字。
然后,我們定義了一個(gè)int變量,然后通過構(gòu)造函數(shù)初始化這個(gè)變量。
你應(yīng)該也清楚了,括號(hào)里的數(shù)字,其實(shí)就是我們定義的那個(gè)int變量。這句叫做自定義變量。
請(qǐng)注意:這里有三點(diǎn)需要注意:
- 一定要把枚舉變量的定義放在第一行,并且以分號(hào)結(jié)尾。
- 構(gòu)造函數(shù)必須私有化。事實(shí)上,private是多余的,你完全沒有必要寫,因?yàn)樗J(rèn)并強(qiáng)制是private,如果你要寫,也只能寫private,寫public是不能通過編譯的。
- 自定義變量與默認(rèn)的ordinal屬性并不沖突,ordinal還是按照它的規(guī)則給每個(gè)枚舉變量按順序賦值。
好了,你很聰明,你已經(jīng)掌握了上面的知識(shí),你想,既然能自定義一個(gè)變量,能不能自定義兩個(gè)呢?
當(dāng)然可以:
public enum Weekday {
MON(1,"mon"),TUS(2,"tus"),WED(3,"wed"),THU(4,"thu"),FRI(5,"fri"),SAT(6,"sat"),SUN(0,"sun");
private int value;
private String label;
private Weekday(int value,String label){
this.value = value;
this.label = label;
}
}
你可以定義任何你想要的變量。學(xué)完了這些,大概枚舉類你也應(yīng)該掌握了,但是,還有沒有其他用法呢?
枚舉類中的抽象類
如果我在枚舉類中定義一個(gè)抽象方法會(huì)怎么樣?
你要知道,枚舉類不能繼承其他類,也不能被其他類繼承。至于為什么,我們后面會(huì)說到。
你應(yīng)該知道,有抽象方法的類必然是抽象類,抽象類就需要子類繼承它然后實(shí)現(xiàn)它的抽象方法,但是呢,枚舉類不能被繼承。。你是不是有點(diǎn)亂?
我們先來看代碼:
public enum TrafficLamp {
RED(30) {
@Override
public TrafficLamp getNextLamp() {
return GREEN;
}
}, GREEN(45) {
@Override
public TrafficLamp getNextLamp() {
return YELLOW;
}
}, YELLOW(5) {
@Override
public TrafficLamp getNextLamp() {
return RED;
}
};
private int time;
private TrafficLamp(int time) {
this.time = time;
}
//一個(gè)抽象方法
public abstract TrafficLamp getNextLamp();
}
你好像懂了點(diǎn)什么。但是你好像又不太懂。為什么一個(gè)變量的后邊可以帶一個(gè)代碼塊并且實(shí)現(xiàn)抽象方法呢?
別著急,帶著這個(gè)疑問,我們來看一下枚舉類的實(shí)現(xiàn)原理。
枚舉類的實(shí)現(xiàn)原理
從最簡(jiǎn)單的看起:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
還是這段熟悉的代碼,我們編譯一下它,再反編譯一下看看它到底是什么樣子的:
反編譯代碼
你是不是覺得很熟悉?反編譯出來的代碼和我們一開始用靜態(tài)變量自己寫的那個(gè)類出奇的相似!
而且,你看到了熟悉的values()方法和valueOf()方法。
仔細(xì)看,這個(gè)類繼承了java.lang.Enum類!所以說,枚舉類不能再繼承其他類了,因?yàn)槟J(rèn)已經(jīng)繼承了Enum類。
并且,這個(gè)類是final的!所以它不能被繼承!
回到我們剛才的那個(gè)疑問:
RED(30) {
@Override
public TrafficLamp getNextLamp() {
return GREEN;
}
}
為什么會(huì)有這么神奇的代碼?現(xiàn)在你差不多懂了。因?yàn)镽ED本身就是一個(gè)TrafficLamp對(duì)象的引用。實(shí)際上,在初始化這個(gè)枚舉類的時(shí)候,你可以理解為執(zhí)行的是TrafficLamp RED = new TrafficLamp(30) ,但是因?yàn)門rafficLamp里面有抽象方法,還記得匿名內(nèi)部類么?
我們可以這樣來創(chuàng)建一個(gè)TrafficLamp引用:
TrafficLamp RED = new TrafficLamp(30){
@Override
public TrafficLamp getNextLamp() {
return GREEN;
}
};
而在枚舉類中,我們只需要像上面那樣寫【RED(30){}】就可以了,因?yàn)閖ava會(huì)自動(dòng)的去幫我們完成這一系列操作。
如果你還是不太理解,那么你可以自己去反編譯一下TrafficLamp這個(gè)類,看看jvm是怎么處理它的就明白了。
枚舉類的其他用法
說一說枚舉類的其他用法。
switch語句中使用
enum Signal {
GREEN, YELLOW, RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
實(shí)現(xiàn)接口
雖然枚舉類不能繼承其他類,但是還是可以實(shí)現(xiàn)接口的
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour {
RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLO("黃色", 4);
// 成員變量
private String name;
private int index;
// 構(gòu)造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 接口方法
@Override
public String getInfo() {
return this.name;
}
// 接口方法
@Override
public void print() {
System.out.println(this.index + ":" + this.name);
}
}
使用接口組織枚舉
public interface Food {
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO
}
enum Dessert implements Food {
FRUIT, CAKE, GELATO
}
}
使用枚舉創(chuàng)建單例模式
使用枚舉創(chuàng)建的單例模式:
public enum EasySingleton{
INSTANCE;
}
代碼就這么簡(jiǎn)單,你可以使用EasySingleton.INSTANCE調(diào)用它,比起你在單例中調(diào)用getInstance()方法容易多了。
我們來看看正常情況下是怎樣創(chuàng)建單例模式的:
用雙檢索實(shí)現(xiàn)單例:
下面的代碼是用雙檢索實(shí)現(xiàn)單例模式的例子,在這里getInstance()方法檢查了兩次來判斷INSTANCE是否為null,這就是為什么叫雙檢索的原因,記住雙檢索在java5之前是有問題的,但是java5在內(nèi)存模型中有了volatile變量之后就沒問題了。
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
你可以訪問DoubleCheckedLockingSingleTon.getInstance()來獲得實(shí)例對(duì)象。
用靜態(tài)工廠方法實(shí)現(xiàn)單例:
public class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
你可以調(diào)用Singleton.getInstance()方法來獲得實(shí)例對(duì)象。
上面的兩種方式就是懶漢式和惡漢式單利的創(chuàng)建,但是無論哪一種,都不如枚舉來的方便。而且傳統(tǒng)的單例模式的另外一個(gè)問題是一旦你實(shí)現(xiàn)了serializable接口,他們就不再是單例的了。但是枚舉類的父類【Enum類】實(shí)現(xiàn)了Serializable接口,也就是說,所有的枚舉類都是可以實(shí)現(xiàn)序列化的,這也是一個(gè)優(yōu)點(diǎn)。
總結(jié)
最后總結(jié)一下:
- 可以創(chuàng)建一個(gè)enum類,把它看做一個(gè)普通的類。除了它不能繼承其他類了。(java是單繼承,它已經(jīng)繼承了Enum),可以添加其他方法,覆蓋它本身的方法
- switch()參數(shù)可以使用enum
- values()方法是編譯器插入到enum定義中的static方法,所以,當(dāng)你將enum實(shí)例向上轉(zhuǎn)型為父類Enum是,values()就不可訪問了。解決辦法:在Class中有一個(gè)getEnumConstants()方法,所以即便Enum接口中沒有values()方法,我們?nèi)匀豢梢酝ㄟ^Class對(duì)象取得所有的enum實(shí)例
- 無法從enum繼承子類,如果需要擴(kuò)展enum中的元素,在一個(gè)接口的內(nèi)部,創(chuàng)建實(shí)現(xiàn)該接口的枚舉,以此將元素進(jìn)行分組。達(dá)到將枚舉元素進(jìn)行分組。
- enum允許程序員為eunm實(shí)例編寫方法。所以可以為每個(gè)enum實(shí)例賦予各自不同的行為。
本文到這里就差不多結(jié)束了??赡芘e得例子不是很恰當(dāng),代碼寫的不是很優(yōu)雅,不過我只是用來引出枚舉的,大家不要雞蛋里頭挑骨頭哈哈。
如果文章內(nèi)容有什么問題,請(qǐng)及時(shí)與我聯(lián)系。
除此之外,還有兩個(gè)枚舉集合:【java.util.EnumSet和java.util.EnumMap】沒有講。關(guān)于枚舉集合的使用會(huì)在后面講集合框架的時(shí)候再詳細(xì)講解。
轉(zhuǎn)載請(qǐng)注明出處:
本文地址:http://blog.csdn.net/qq_31655965/article/details/55049192
原創(chuàng)自csdn:http://blog.csdn.net/qq_31655965
博主:clever_fan
看完了,如果對(duì)你有幫助,隨手點(diǎn)個(gè)贊唄~~~
參考資料:
http://www.cnblogs.com/happyPawpaw/archive/2013/04/09/3009553.html
https://my.oschina.net/zhoujy/blog/134958