絮叨
昨天剛好有遇到一個枚舉的小問題,然後發現自己并不是那麼熟悉它,然後在開發中,枚舉用得特别多,是以有了今天的文章。
什麼是枚舉
Java中的枚舉是一種類型,顧名思義:就是一個一個列舉出來。是以它一般都是表示一個有限的集合類型,它是一種類型,在維基百科中給出的定義是:
在數學和計算機科學理論中,一個集的枚舉是列出某些有窮序列集的所有成員的程式,或者是一種特定類型對象的計數。這兩種類型經常(但不總是)重疊.。枚舉是一個被命名的整型常數的集合,枚舉在日常生活中很常見,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一個枚舉。
出現的原因
在Java5之前,其實是沒有enum的,是以先來看一下Java5之前對于枚舉的使用場景該怎麼解決?這裡我看到了一片關于在Java 1.4之前的枚舉的設計方案:
public class Season { public static final int SPRING = 1; public static final int SUMMER = 2; public static final int AUTUMN = 3; public static final int WINTER = 4;}複制代碼
這種方法稱作int枚舉模式。可這種模式有什麼問題呢?通常我們寫出來的代碼都會考慮它的安全性、易用性和可讀性。 首先我們來考慮一下它的類型安全性。當然這種模式不是類型安全的。比如說我們設計一個函數,要求傳入春夏秋冬的某個值。但是使用int類型,我們無法保證傳入的值為合法。代碼如下所示:
private String getChineseSeason(int season){ StringBuffer result = new StringBuffer(); switch(season){ case Season.SPRING : result.append("春天"); break; case Season.SUMMER : result.append("夏天"); break; case Season.AUTUMN : result.append("秋天"); break; case Season.WINTER : result.append("冬天"); break; default : result.append("地球沒有的季節"); break; } return result.toString(); }複制代碼
因為我們傳值的時候,可能會傳其他的類型,就可能導緻走default,是以這個并不能在源頭上解決類型安全問題。
接下來我們來考慮一下這種模式的可讀性。使用枚舉的大多數場合,我都需要友善得到枚舉類型的字元串表達式。如果将int枚舉常量列印出來,我們所見到的就是一組數字,這是沒什麼太大的用處。我們可能會想到使用String常量代替int常量。雖然它為這些常量提供了可列印的字元串,但是它會導緻性能問題,因為它依賴于字元串的比較操作,是以這種模式也是我們不期望的。 從類型安全性和程式可讀性兩方面考慮,int和String枚舉模式的缺點就顯露出來了。幸運的是,從Java1.5發行版本開始,就提出了另一種可以替代的解決方案,可以避免int和String枚舉模式的缺點,并提供了許多額外的好處。那就是枚舉類型(enum type)。
枚舉定義
枚舉類型(enum type)是指由一組固定的常量組成合法的類型。Java中由關鍵字enum來定義一個枚舉類型。下面就是java枚舉類型的定義。
public enum Season { SPRING, SUMMER, AUTUMN, WINER;}複制代碼
Java定義枚舉類型的語句很簡約。它有以下特點:
- 使用關鍵字enum
- 類型名稱,比如這裡的Season
- 一串允許的值,比如上面定義的春夏秋冬四季
- 枚舉可以單獨定義在一個檔案中,也可以嵌在其它Java類中
- 枚舉可以實作一個或多個接口(Interface)
- 可以定義新的變量和方法
重寫上面的枚舉方式
下面是一個很規範的枚舉類型
public enum Season { SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4); private int code; private Season(int code){ this.code = code; } public int getCode(){ return code; }}public class UseSeason { /** * 将英文的季節轉換成中文季節 * @param season * @return */ public String getChineseSeason(Season season){ StringBuffer result = new StringBuffer(); switch(season){ case SPRING : result.append("[中文:春天,枚舉常量:" + season.name() + ",資料:" + season.getCode() + "]"); break; case AUTUMN : result.append("[中文:秋天,枚舉常量:" + season.name() + ",資料:" + season.getCode() + "]"); break; case SUMMER : result.append("[中文:夏天,枚舉常量:" + season.name() + ",資料:" + season.getCode() + "]"); break; case WINTER : result.append("[中文:冬天,枚舉常量:" + season.name() + ",資料:" + season.getCode() + "]"); break; default : result.append("地球沒有的季節 " + season.name()); break; } return result.toString(); } public void doSomething(){ for(Season s : Season.values()){ System.out.println(getChineseSeason(s));//這是正常的場景 } //System.out.println(getChineseSeason(5)); //此處已經是編譯不通過了,這就保證了類型安全 } public static void main(String[] arg){ UseSeason useSeason = new UseSeason(); useSeason.doSomething(); }}複制代碼
Enum類的常用方法
values() 方法
通過調用枚舉類型執行個體的 values() 方法可以将枚舉的所有成員以數組形式傳回,也可以通過該方法擷取枚舉類型的成員。
下面的示例建立一個包含 3 個成員的枚舉類型 Signal,然後調用 values() 方法輸出這些成員。
public enum Signal { //定義一個枚舉類型 GREEN,YELLOW,RED; public static void main(String[] args) { for(int i=0;i
結果
//枚舉成員:GREEN//枚舉成員:YELLOW//枚舉成員:RED複制代碼
valueOf方法
通過字元串擷取單個枚舉對象
public enum Signal { //定義一個枚舉類型 GREEN,YELLOW,RED; public static void main(String[] args) { Signal green = Signal.valueOf("GREEN"); System.out.println(green); }}複制代碼
結果
//GREEN複制代碼
ordinal() 方法
通過調用枚舉類型執行個體的 ordinal() 方法可以擷取一個成員在枚舉中的索引位置。下面的示例建立一個包含 3 個成員的枚舉類型 Signal,然後調用 ordinal() 方法輸出成員及對應索引位置。
public class TestEnum1{ enum Signal { //定義一個枚舉類型 GREEN,YELLOW,RED; } public static void main(String[] args) { for(int i=0;i
結果
//索引0,值:GREEN//索引1,值:YELLOW//索引2,值:RED複制代碼
枚舉實作單例
使用枚舉實作單例的方法雖然還沒有廣泛采用,但是單元素的枚舉類型已經成為實作Singleton的最佳方法。
單例的餓漢式
package com.atguigu.ct.producer.controller;//final 不允許被繼承public final class HungerSingleton {private static HungerSingleton instance=new HungerSingleton(); //私有構造函數不允許外部new private HungerSingleton(){ } //提供一個方法給外部調用 public static HungerSingleton getInstance(){ return instance; }}複制代碼
懶漢式的單例
package com.atguigu.ct.producer.controller;//final 不能被繼承public final class DoubleCheckedSingleton { //定義執行個體但是不直接初始化,volatile禁止重排序操作,避免空指針異常 private static DoubleCheckedSingleton instance=new DoubleCheckedSingleton(); //私有構造函數不允許外部new private DoubleCheckedSingleton(){ } //對外提供的方法用來擷取單例對象 public static DoubleCheckedSingleton getInstance(){ if(null==instance){ synchronized (DoubleCheckedSingleton.class){ if (null==instance) { instance = new DoubleCheckedSingleton(); } } } return instance; } }複制代碼
枚舉的單例
package com.atguigu.ct.producer.controller;public enum EnumSingleton { //定義一個單例對象 INSTANCE; //擷取單例對象的方法 public static EnumSingleton getInstance(){ return INSTANCE; }}複制代碼
在一個需要單例的類裡面,定義一個靜态枚舉類,來實作枚舉的單例
看上面三個方式,光看代碼就知道單例的模式是最簡單的,因為單例本身就是私有構造的,是以建議大家以後用枚舉來實作單例
面試問題
枚舉允許繼承類嗎?
枚舉不允許繼承類。Jvm在生成枚舉時已經繼承了Enum類,由于Java語言是單繼承,不支援再繼承額外的類(唯一的繼承名額被Jvm用了)。
枚舉可以用等号比較嗎?
枚舉可以用等号比較。Jvm會為每個枚舉執行個體對應生成一個類對象,這個類對象是用public static final修飾的,在static代碼塊中初始化,是一個單例。
枚舉可以被人家繼承嗎
不可以繼承枚舉。因為Jvm在生成枚舉類時,将它聲明為final。
結尾
枚舉其實也就那麼多了,定義常量的話用枚舉确實是優雅很多,大家在項目中記得多使用哈