?Java基礎之Java列舉

六脈神劍發表於2020-02-13

前言

文字已收錄至我的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。

結尾

列舉其實也就那麼多了,定義常量的話用列舉確實是優雅很多,大家在專案中記得多使用哈

日常求贊

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是真粉

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見

六脈神劍 | 文 【原創】如果本篇部落格有任何錯誤,請批評指教,不勝感激 !

相關文章