Java中的枚舉類型詳解

 枚舉類型介紹java

  枚舉類型(Enumerated Type) 很早就出如今編程語言中,它被用來將一組相似的值包含到一種類型當中。而這種枚舉類型的名稱則會被定義成獨一無二的類型描述符,在這一點上和常量的定義類似。不過相比較常量類型,枚舉類型能夠爲申明的變量提供更大的取值範圍。程序員

  舉個例子來講明一下,若是但願爲彩虹描繪出七種顏色,你能夠在 Java 程序中經過常量定義方式來實現。編程

                
 Public static class RainbowColor { 
    
    // 紅橙黃綠青藍紫七種顏色的常量定義
    public static final int RED = 0; 
    public static final int ORANGE = 1; 
    public static final int YELLOW = 2; 
    public static final int GREEN = 3; 
    public static final int CYAN = 4; 
    public static final int BLUE = 5; 
    public static final int PURPLE = 6; 
 } 

使用的時候,你能夠在程序中直接引用這些常量。可是,這種方式仍是存在着一些問題。數組

  1. 類型不安全

因爲顏色常量的對應值是整數形,因此程序執行過程當中頗有可能給顏色變量傳入一個任意的整數值,致使出現錯誤。安全

  1. 沒有命名空間

因爲顏色常量只是類的屬性,當你使用的時候不得不經過類來訪問。服務器

  1. 一致性差

由於整形枚舉屬於編譯期常量,因此編譯過程完成後,全部客戶端和服務器端引用的地方,會直接將整數值寫入。這樣,當你修改舊的枚舉整數值後或者增長新的枚舉值後,全部引用地方代碼都須要從新編譯,不然運行時刻就會出現錯誤。網絡

  1. 類型無指意性

因爲顏色枚舉值僅僅是一些無任何含義的整數值,若是在運行期調試時候,你就會發現日誌中有不少魔術數字,但除了程序員自己,其餘人很難明白其奧祕。socket

  如何定義枚舉類型編程語言

  爲了改進 Java 語言在這方面的不足彌補缺陷,5.0 版本 SDK 發佈時候,在語言層面上增長了枚舉類型。枚舉類型的定義也很是的簡單,用 enum 關鍵字加上名稱和大括號包含起來的枚舉值體便可,例如上面提到的彩虹顏色就能夠用新的 enum 方式來從新定義:分佈式

enum RainbowColor { RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE } 

從上面的定義形式來看,彷佛 Java 中的枚舉類型很簡單,但實際上 Java 語言規範賦予枚舉類型的功能很是的強大,它不只是簡單地將整形數值轉換成對象,而是將枚舉類型定義轉變成一個完整功能的類定義。這種類型定義的擴展容許開發者給枚舉類型增長任何方法和屬性,也能夠實現任意的接口。另外,Java 平臺也爲 Enum 類型提供了高質量的實現,好比默認實現 Comparable 和 Serializable 接口,讓開發者通常狀況下不用關心這些細節。

回到本文的主題上來,引入枚舉類型到底可以給咱們開發帶來什麼樣好處呢?一個最直接的益處就是擴大 switch 語句使用範圍。5.0 以前,Java 中 switch 的值只可以是簡單類型,好比 int、long、char, 有了枚舉類型以後,就可使用對象了。這樣一來,程序的控制選擇就變得更加的方便,看下面的例子:

// 定義一週七天的枚舉類型            
 public enum WeekDayEnum { Mon, Tue, Wed, Thu, Fri, Sat, Sun } 

 // 讀取當天的信息
 WeekDayEnum today = readToday(); 
 
 // 根據日期來選擇進行活動
 switch(today) { 
  Mon: do something; break; 
  Tue: do something; break; 
  Wed: do something; break; 
  Thu: do something; break; 
  Fri: do something; break; 
  Sat: play sports game; break; 
  Sun: have a rest; break; 
 } 

  對於這些枚舉的日期,JVM 都會在運行期構形成出一個簡單的對象實例一一對應。這些對象都有惟一的 identity,相似整形數值同樣,switch 語句就根據此來進行執行跳轉。

  如何定製枚舉類型

  除了以上這種最多見的枚舉定義形式外,若是須要給枚舉類型增長一些複雜功能,也能夠經過相似 class 的定義來給枚舉進行定製。好比要給 enum 類型增長屬性,能夠像下面這樣定義:

// 定義 RSS(Really Simple Syndication) 種子的枚舉類型
 public enum NewsRSSFeedEnum { 
    // 雅虎頭條新聞 RSS 種子
    YAHOO_TOP_STORIES("http://rss.news.yahoo.com/rss/topstories"), 
    
    //CBS 頭條新聞 RSS 種子
    CBS_TOP_STORIES("http://feeds.cbsnews.com/CBSNewsMain?format=xml"), 
    
    // 洛杉磯時報頭條新聞 RSS 種子
    LATIMES_TOP_STORIES("http://feeds.latimes.com/latimes/news?format=xml"); 
        
    // 枚舉對象的 RSS 地址的屬性
    private String rss_url; 
        
    // 枚舉對象構造函數
    private NewsRSSFeedEnum(String rss) { 
        this.rss_url = rss; 
    } 
        
    // 枚舉對象獲取 RSS 地址的方法
    public String getRssURL() { 
        return this.rss_url; 
    } 
 } 

  上面頭條新聞的枚舉類型增長了一個 RSS 地址的屬性 , 記錄頭條新聞的訪問地址。同時,須要外部傳入 RSS 訪問地址的值,於是須要定義一個構造函數來初始化此屬性。另外,還須要向外提供方法來讀取 RSS 地址。

  如何避免錯誤使用枚舉類型

  不過在使用 Enum 時候有幾個地方須要注意:

  1. enum 類型不支持 public 和 protected 修飾符的構造方法,所以構造函數必定要是 private 或 friendly 的。也正由於如此,因此枚舉對象是沒法在程序中經過直接調用其構造方法來初始化的。
  2. 定義 enum 類型時候,若是是簡單類型,那麼最後一個枚舉值後不用跟任何一個符號;但若是有定製方法,那麼最後一個枚舉值與後面代碼要用分號';'隔開,不能用逗號或空格。
  3. 因爲 enum 類型的值其實是經過運行期構造出對象來表示的,因此在 cluster 環境下,每一個虛擬機都會構造出一個同義的枚舉對象。於是在作比較操做時候就須要注意,若是直接經過使用等號 ( ‘ == ’ ) 操做符,這些看似同樣的枚舉值必定不相等,由於這不是同一個對象實例。

看下面的這個例子:

// 定義一個一週七天的枚舉類型
 package example.enumeration.codes; 

 public enum WeekDayEnum { 
    Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6), Sun(7); 

    private int index; 

    WeekDayEnum(int idx) { 
        this.index = idx; 
    } 

    public int getIndex() { 
        return index; 
    } 
 } 

 // 客戶端程序,將一個枚舉值經過網絡傳遞給服務器端
 package example.enumeration.codes; 

 import java.io.IOException; 
 import java.io.ObjectOutputStream; 
 import java.io.OutputStream; 
 import java.net.InetSocketAddress; 
 import java.net.Socket; 
 import java.net.UnknownHostException; 

 public class EnumerationClient { 

    public static void main(String... args) throws UnknownHostException, IOException { 
        Socket socket = new Socket(); 
  // 創建到服務器端的鏈接
        socket.connect(new InetSocketAddress("127.0.0.1", 8999)); 
    // 從鏈接中獲得輸出流
        OutputStream os = socket.getOutputStream(); 
        ObjectOutputStream oos = new ObjectOutputStream(os); 
  // 將星期五這個枚舉值傳遞給服務器端
        oos.writeObject(WeekDayEnum.Fri); 
        oos.close(); 
        os.close(); 
        socket.close(); 
    } 
 } 

 // 服務器端程序,將從客戶端收到的枚舉值應用到邏輯處理中
 package example.enumeration.codes; 

 import java.io.*; 
 import java.net.ServerSocket; 
 import java.net.Socket; 

 public class EnumerationServer { 

    public static void main(String... args) throws IOException, ClassNotFoundException { 
        ServerSocket server = new ServerSocket(8999); 
  // 創建服務器端的網絡鏈接偵聽
        Socket socket = server.accept(); 
  // 從鏈接中獲取輸入流
        InputStream is = socket.getInputStream(); 
        ObjectInputStream ois = new ObjectInputStream(is); 
  // 讀出客戶端傳遞來的枚舉值
        WeekDayEnum day = (WeekDayEnum) ois.readObject(); 
  // 用值比較方式來對比枚舉對象
        if (day == WeekDayEnum.Fri) { 
            System.out.println("client Friday enum value is same as server's"); 
        } else if (day.equals(WeekDayEnum.Fri)) { 
            System.out.println("client Friday enum value is equal to server's"); 
        } else { 
            System.out.println("client Friday enum value is not same as server's"); 
        } 
        
  // 用 switch 方式來比較枚舉對象
        switch (day) { 
            case Mon: 
                System.out.println("Do Monday work"); 
                break; 
            case Tue: 
                System.out.println("Do Tuesday work"); 
                break; 
            case Wed: 
                System.out.println("Do Wednesday work"); 
                break; 
            case Thu: 
                System.out.println("Do Thursday work"); 
                break; 
            case Fri: 
                System.out.println("Do Friday work"); 
                break; 
            case Sat: 
                System.out.println("Do Saturday work"); 
                break; 
            case Sun: 
                System.out.println("Do Sunday work"); 
                break; 
            default: 
                System.out.println("I don't know which is day"); 
                break; 
        } 
        
        ois.close(); 
        is.close(); 
        socket.close(); 
    } 
 } 

運行結果以下:

client Friday enum value is same as server's 
 Do Friday work 

  經過程序執行結果,咱們可以發如今分佈式條件下客戶端和服務端的虛擬機上都生成了一個枚舉對象,即便看起來同樣的 Fri 枚舉值,若是使用等號‘ == ’進行比較的話會出現不等的狀況。而 switch 語句則是經過 equal 方法來比較枚舉對象的值,所以當你的枚舉對象較複雜時候,你就須要當心 override 與比較相關的方法,防止出現值比較方面的錯誤。

  枚舉類型相關工具類

     DK5.0 中在增長 Enum 類的同時,也增長了兩個工具類 EnumSet 和 EnumMap,這兩個類都放在 java.util 包中。EnumSet 是一個針對枚舉類型的高性能的 Set 接口實現。EnumSet 中裝入的全部枚舉對象都必須是同一種類型,在其內部,是經過 bit-vector 來實現,也就是經過一個 long 型數。EnumSet 支持在枚舉類型的全部值的某個範圍中進行迭代。回到上面日期枚舉的例子上:

 enum WeekDayEnum { Mon, Tue, Wed, Thu, Fri, Sat, Sun } 

你可以在每週七天日期中進行迭代,EnumSet 類提供一個靜態方法 range 讓迭代很容易完成:

for(WeekDayEnum day : EnumSet.range(WeekDayEnum.Mon, WeekDayEnum.Fri)) { 
     System.out.println(day); 
 } 

打印結果:

Mon 
 Wed 

 EnumSet 相似,EnumMap 也是一個高性能的 Map 接口實現,用來管理使用枚舉類型做爲 keys 的映射表,內部是經過數組方式來實現。EnumMap 將豐富的和安全的 Map 接口與數組快速訪問結合到一塊兒,若是你但願要將一個枚舉類型映射到一個值,你應該使用 EnumMap。看下面的例子:

// 定義一個 EnumMap 對象,映射表主鍵是日期枚舉類型,值是顏色枚舉類型
 private static Map<WeekDayEnum, RainbowColor> schema = 
            new EnumMap<WeekDayEnum, RainbowColor>(WeekDayEnum.class); 
    
 static{ 
    // 將一週的每一天與彩虹的某一種色彩映射起來
    for (int i = 0; i < WeekDayEnum.values().length; i++) { 
        schema.put(WeekDayEnum.values()[i], RainbowColor.values()[i]); 
    } 
 } 
 System.out.println("What is the lucky color today?"); 
 System.out.println("It's " + schema.get(WeekDayEnum.Sat)); 

當你詢問週六的幸運色彩時候,會獲得藍色:

What is the lucky color today? 
 It's BLUE 
相關文章
相關標籤/搜索