有的人說,不推薦使用枚舉。有的人說,枚舉很好用。究竟怎麼使用,如何使用,仁者見仁智者見智。總之,先學會再說~
爲什麼要引入枚舉類
一個小案例
你寫了一個小程序,不過好久不用了,突然有一天,你想使用一下它。程序要想正確運行,需要將今天星期幾存到數據庫裏。這個時候,你開始犯難了。
當初的你還很年輕,不懂程序界的險惡,設計這個程序的時候,傻不拉幾把這個字段設計爲int類型的,用0代表週日,1代表週一。。。6代表週六,添加的時候就setWeekday(0)。但是這麼長時間沒用了,你忘記自己是從週一開始計算還是週日開始計算了,換句話說,你想不起來0代表的是週一還是週日了!
於是你各種翻代碼,看數據庫的字段,數據庫保存的信息,終於搞懂了,你很開心,用了一次之後,覺得這個程序沒意思,又不用了。
很久之後,你心血來潮,又想用一次它,很不幸,你又忘記到底0代表週一還是週日了,一番查找之後。你決定重構代碼,因爲你受不了了!!
靜態變量來幫忙
經過一番思考,你決定使用七個靜態變量來代表星期幾,以後只要引用和靜態變量就可以了,而不用自己輸入012….你這麼寫:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
機智如你,這個時候,只要Weekday.SUN
就可以了,不用操心到底應該填寫0還是填寫1。
但是這個時候的你,也不是當初初出茅廬的小夥子了,很明顯,這樣寫已經不能滿足你了。你還想讓這個類做更多的事,比如,你想知道下一天是星期幾,還想把今天是星期幾打印出來。一番深思熟慮後,你改成了這樣:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
喲,不錯。考慮的很詳細。並且私有構造方法後,外界就不能創建該類的對象了,這樣就避免了星期八星期九的出現,所有Weekday的對象都在該類內部創建。
不對,好像缺了點什麼,我要的是int!我的int呢?!。所以,你還需要一個這樣的方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
當你需要一個整形數據的時候,只需要Weekday.toInt(Weekday.SUN);
,看起來你好像完成了你的任務。
但是,你有沒有發現,這樣寫,好麻煩啊。如果想要擴展一下功能,大量的ifelse會讓人眼花繚亂。
有沒有更好的方式呢?你大概已經知道了,沒錯,我們需要枚舉類!
我們先來看看枚舉類是什麼。
一個簡單的枚舉類
話不多說,先來代碼:
- 1
- 2
- 3
- 1
- 2
- 3
代碼這麼少?
沒錯,這就是枚舉類,我們來看看怎麼使用它:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
看起來和上面的靜態變量使用方式差不多,而且默認的toString方法返回的就是對應的名字。
我們上面的那段代碼重寫toString也是不可以打印出當前是星期幾的,因爲toString方法沒有參數。所以我們自己寫了一個printNowDay方法。
當然,這麼簡單的枚舉類是不可能實現我們的要求的,所以,我們還要接着寫:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
這樣就完成了我們的目標,和之前的代碼比起來,有沒有覺得突然高大上了許多?沒有那麼多煩人的ifelse,世界都清淨了。
好了,現在你大概知道爲什麼要引入枚舉類了吧?就是因爲在沒有枚舉類的時候,我們要定義一個有限的序列,比如星期幾,男人女人,春夏秋冬,一般會通過上面那種靜態變量的形式,但是使用那樣的形式如果需要一些其他的功能,需要些很多奇奇怪怪的代碼。所以,枚舉類的出現,就是爲了簡化這種操作。
可以將枚舉類理解爲是Java的一種語法糖。
枚舉類的用法
最簡單的使用
最簡單的枚舉類就像我們上面第一個定義的枚舉類一樣:
- 1
- 2
- 3
- 1
- 2
- 3
如何使用它呢?
先來看看它有哪些方法:
這是Weekday可以調用的方法和參數。發現它有兩個方法:value()和valueOf()。還有我們剛剛定義的七個變量。
這些事枚舉變量的方法。我們接下來會演示幾個比較重要的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
這段代碼,我們演示了幾個常用的方法和功能:
-
Weekday.valueOf() 方法:
它的作用是傳來一個字符串,然後將它轉變爲對應的枚舉變量。前提是你傳的字符串和定義枚舉變量的字符串一抹一樣,區分大小寫。如果你傳了一個不存在的字符串,那麼會拋出異常。
-
Weekday.values()方法。
這個方法會返回包括所有枚舉變量的數組。在該例中,返回的就是包含了七個星期的Weekday[]。可以方便的用來做循環。
-
枚舉變量的toString()方法。
該方法直接返回枚舉定義枚舉變量的字符串,比如MON就返回【”MON”】。
-
枚舉變量的.ordinal()方法。
默認情況下,枚舉類會給所有的枚舉變量一個默認的次序,該次序從0開始,類似於數組的下標。而.ordinal()方法就是獲取這個次序(或者說下標)
-
枚舉變量的compareTo()方法。
該方法用來比較兩個枚舉變量的”大小”,實際上比較的是兩個枚舉變量的次序,返回兩個次序相減後的結果,如果爲負數,就證明變量1”小於”變量2 (變量1.compareTo(變量2),返回【變量1.ordinal() - 變量2.ordinal()】)
這是compareTo的源碼,會先判斷是不是同一個枚舉類的變量,然後再返回差值。
-
枚舉類的name()方法。
它和toString()方法的返回值一樣,事實上,這兩個方法本來就是一樣的:
這兩個方法的默認實現是一樣的,唯一的區別是,你可以重寫toString方法。name變量就是枚舉變量的字符串形式。
還有一些其他的方法我就暫時不介紹了,感興趣的話可以自己去看看文檔或者源碼,都挺簡單的。
要點:
- 使用的是enum關鍵字而不是class。
- 多個枚舉變量直接用逗號隔開。
- 枚舉變量最好大寫,多個單詞之間使用”_”隔開(比如:INT_SUM)。
- 定義完所有的變量後,以分號結束,如果只有枚舉變量,而沒有自定義變量,分號可以省略(例如上面的代碼就忽略了分號)。
- 在其他類中使用enum變量的時候,只需要【類名.變量名】就可以了,和使用靜態變量一樣。
但是這種簡單的使用顯然不能體現出枚舉的強大,我們來學習一下複雜的使用:
枚舉的高級使用方法
就像我們前面的案例一樣,你需要讓每一個星期幾對應到一個整數,比如星期天對應0。上面講到了,枚舉類在定義的時候會自動爲每個變量添加一個順序,從0開始。
假如你希望0代表星期天,1代表週一。。。並且你在定義枚舉類的時候,順序也是這個順序,那你可以不用定義新的變量,就像這樣:
- 1
- 2
- 3
- 1
- 2
- 3
這個時候,星期天對應的ordinal值就是0,週一對應的就是1,滿足你的要求。但是,如果你這麼寫,那就有問題了:
- 1
- 2
- 3
- 1
- 2
- 3
我吧SUN放到了最後,但是我還是希0代表SUN,1代表MON怎麼辦呢?默認的ordinal是指望不上了,因爲它只會傻傻的給第一個變量0,給第二個1。。。
所以,我們需要自己定義變量!
看代碼:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
我們對上面的代碼做了一些改變:
首先,我們在每個枚舉變量的後面加上了一個括號,裏面是我們希望它代表的數字。
然後,我們定義了一個int變量,然後通過構造函數初始化這個變量。
你應該也清楚了,括號裏的數字,其實就是我們定義的那個int變量。這句叫做自定義變量。
請注意:這裏有三點需要注意:
- 一定要把枚舉變量的定義放在第一行,並且以分號結尾。
- 構造函數必須私有化。事實上,private是多餘的,你完全沒有必要寫,因爲它默認並強制是private,如果你要寫,也只能寫private,寫public是不能通過編譯的。
- 自定義變量與默認的ordinal屬性並不衝突,ordinal還是按照它的規則給每個枚舉變量按順序賦值。
好了,你很聰明,你已經掌握了上面的知識,你想,既然能自定義一個變量,能不能自定義兩個呢?
當然可以:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
你可以定義任何你想要的變量。學完了這些,大概枚舉類你也應該掌握了,但是,還有沒有其他用法呢?
枚舉類中的抽象類
如果我在枚舉類中定義一個抽象方法會怎麼樣?
你要知道,枚舉類不能繼承其他類,也不能被其他類繼承。至於爲什麼,我們後面會說到。
你應該知道,有抽象方法的類必然是抽象類,抽象類就需要子類繼承它然後實現它的抽象方法,但是呢,枚舉類不能被繼承。。你是不是有點亂?
我們先來看代碼:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
你好像懂了點什麼。但是你好像又不太懂。爲什麼一個變量的後邊可以帶一個代碼塊並且實現抽象方法呢?
彆着急,帶着這個疑問,我們來看一下枚舉類的實現原理。
枚舉類的實現原理
從最簡單的看起:
- 1
- 2
- 3
- 1
- 2
- 3
還是這段熟悉的代碼,我們編譯一下它,再反編譯一下看看它到底是什麼樣子的:
你是不是覺得很熟悉?反編譯出來的代碼和我們一開始用靜態變量自己寫的那個類出奇的相似!
而且,你看到了熟悉的values()方法和valueOf()方法。
仔細看,這個類繼承了java.lang.Enum類!所以說,枚舉類不能再繼承其他類了,因爲默認已經繼承了Enum類。
並且,這個類是final的!所以它不能被繼承!
回到我們剛纔的那個疑問:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
爲什麼會有這麼神奇的代碼?現在你差不多懂了。因爲RED本身就是一個TrafficLamp對象的引用。實際上,在初始化這個枚舉類的時候,你可以理解爲執行的是TrafficLamp RED = new TrafficLamp(30)
,但是因爲TrafficLamp裏面有抽象方法,還記得匿名內部類麼?
我們可以這樣來創建一個TrafficLamp引用:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
而在枚舉類中,我們只需要像上面那樣寫【RED(30){}
】就可以了,因爲java會自動的去幫我們完成這一系列操作。
如果你還是不太理解,那麼你可以自己去反編譯一下TrafficLamp這個類,看看jvm是怎麼處理它的就明白了。
枚舉類的其他用法
說一說枚舉類的其他用法。
switch語句中使用
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
實現接口
雖然枚舉類不能繼承其他類,但是還是可以實現接口的
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
使用接口組織枚舉
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
使用枚舉創建單例模式
使用枚舉創建的單例模式:
- 1
- 2
- 3
- 1
- 2
- 3
代碼就這麼簡單,你可以使用EasySingleton.INSTANCE調用它,比起你在單例中調用getInstance()方法容易多了。
我們來看看正常情況下是怎樣創建單例模式的:
用雙檢索實現單例:
下面的代碼是用雙檢索實現單例模式的例子,在這裏getInstance()方法檢查了兩次來判斷INSTANCE是否爲null,這就是爲什麼叫雙檢索的原因,記住雙檢索在java5之前是有問題的,但是java5在內存模型中有了volatile變量之後就沒問題了。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
你可以訪問DoubleCheckedLockingSingleTon.getInstance()來獲得實例對象。
用靜態工廠方法實現單例:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
你可以調用Singleton.getInstance()方法來獲得實例對象。
上面的兩種方式就是懶漢式和惡漢式單利的創建,但是無論哪一種,都不如枚舉來的方便。而且傳統的單例模式的另外一個問題是一旦你實現了serializable接口,他們就不再是單例的了。但是枚舉類的父類【Enum類】實現了Serializable接口,也就是說,所有的枚舉類都是可以實現序列化的,這也是一個優點。
EnumMap和EnumSet的使用:
package com.csdn.myEnum;
import java.util.EnumMap; import java.util.EnumSet;
public class LightTest {
// 1. 定義枚舉類型 public enum Light { // 利用構造函數傳參 RED (1), GREEN (3), YELLOW (2);
// 定義私有變量 private int nCode ;
// 構造函數,枚舉類型只能爲私有 private Light( int _nCode) { this . nCode = _nCode; }
@Override public String toString() { return String.valueOf ( this . nCode ); } }
/** * @param args */ public static void main(String[] args ) {
// 1. 遍歷枚舉類型 System. out .println( " 演示枚舉類型的遍歷 ......" ); testTraversalEnum ();
// 2. 演示 EnumMap 對象的使用 System. out .println( " 演示 EnmuMap 對象的使用和遍歷 ....." ); testEnumMap ();
// 3. 演示 EnmuSet 的使用 System. out .println( " 演示 EnmuSet 對象的使用和遍歷 ....." ); testEnumSet (); }
/** * 演示枚舉類型的遍歷 */ private static void testTraversalEnum() { Light[] allLight = Light.values (); for (Light aLight : allLight) { System. out .println( " 當前燈 name : " + aLight.name()); System. out .println( " 當前燈 ordinal : " + aLight.ordinal()); System. out .println( " 當前燈: " + aLight); } }
/** * 演示 EnumMap 的使用, EnumMap 跟 HashMap 的使用差不多,只不過 key 要是枚舉類型 */ private static void testEnumMap() { // 1. 演示定義 EnumMap 對象, EnumMap 對象的構造函數需要參數傳入 , 默認是key 的類的類型 EnumMap<Light, String> currEnumMap = new EnumMap<Light, String>( Light. class ); currEnumMap.put(Light. RED , " 紅燈 " ); currEnumMap.put(Light. GREEN , " 綠燈 " ); currEnumMap.put(Light. YELLOW , " 黃燈 " );
// 2. 遍歷對象 for (Light aLight : Light.values ()) { System. out .println( "[key=" + aLight.name() + ",value=" + currEnumMap.get(aLight) + "]" ); } }
/** * 演示 EnumSet 如何使用, EnumSet 是一個抽象類,獲取一個類型的枚舉類型內容<BR/> * 可以使用 allOf 方法 */ private static void testEnumSet() { EnumSet<Light> currEnumSet = EnumSet.allOf (Light. class ); for (Light aLightSetElement : currEnumSet) { System. out .println( " 當前 EnumSet 中數據爲: " + aLightSetElement); }
} }
|
執行結果如下:
演示枚舉類型的遍歷 ...... 當前燈 name : RED 當前燈 ordinal : 0 當前燈: 1 當前燈 name : GREEN 當前燈 ordinal : 1 當前燈: 3 當前燈 name : YELLOW 當前燈 ordinal : 2 當前燈: 2 演示 EnmuMap 對象的使用和遍歷 ..... [key=RED,value= 紅燈 ] [key=GREEN,value= 綠燈 ] [key=YELLOW,value= 黃燈 ] 演示 EnmuSet 對象的使用和遍歷 ..... 當前 EnumSet 中數據爲: 1 當前 EnumSet 中數據爲: 3 當前 EnumSet 中數據爲: 2
|
總結
最後總結一下:
- 可以創建一個enum類,把它看做一個普通的類。除了它不能繼承其他類了。(java是單繼承,它已經繼承了Enum),可以添加其他方法,覆蓋它本身的方法
- switch()參數可以使用enum
- values()方法是編譯器插入到enum定義中的static方法,所以,當你將enum實例向上轉型爲父類Enum是,values()就不可訪問了。解決辦法:在Class中有一個getEnumConstants()方法,所以即便Enum接口中沒有values()方法,我們仍然可以通過Class對象取得所有的enum實例
- 無法從enum繼承子類,如果需要擴展enum中的元素,在一個接口的內部,創建實現該接口的枚舉,以此將元素進行分組。達到將枚舉元素進行分組。
- enum允許程序員爲eunm實例編寫方法。所以可以爲每個enum實例賦予各自不同的行爲。
轉載自:
http://blog.csdn.net/qq_31655965/article/details/55049192
http://blog.csdn.net/wgw335363240/article/details/6359614