前言
文字已收錄至我的GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩qq了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習群,群聊號碼:549684836 鼓勵大家在技術的路上寫部落格
絮叨
昨天剛好有遇到一個列舉的小問題,然後發現自己並不是那麼熟悉它,然後在開發中,列舉用的特別多,所以有了今天的文章。
什麼是列舉
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() | 以陣列形式返回列舉型別的所有成員 |
valueOf() | 將普通字串轉換為列舉例項 |
compareTo() | 比較兩個列舉成員在定義時的順序 |
ordinal() | 獲取列舉成員的索引位置 |
values() 方法
通過呼叫列舉型別例項的 values() 方法可以將列舉的所有成員以陣列形式返回,也可以通過該方法獲取列舉型別的成員。
下面的示例建立一個包含 3 個成員的列舉型別 Signal,然後呼叫 values() 方法輸出這些成員。
public enum Signal {
//定義一個列舉型別
GREEN,YELLOW,RED;
public static void main(String[] args)
{
for(int i=0;i<Signal.values().length;i++)
{
System.out.println("列舉成員:"+Signal.values()[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<Signal.values().length;i++)
{
System.out.println("索引"+Signal.values()[i].ordinal()+",值:"+Signal.values()[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。
結尾
列舉其實也就那麼多了,定義常量的話用列舉確實是優雅很多,大家在專案中記得多使用哈
日常求贊
好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是真粉。
創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見
六脈神劍 | 文 【原創】如果本篇部落格有任何錯誤,請批評指教,不勝感激 !