更多內容請關注微信公衆號【Java技術江湖】java
這是一位阿里 Java 工程師的技術小站,做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!(關注公衆號後回覆」資料「便可領取 3T 免費技術學習資源以及我我原創的程序員校招指南、Java學習指南等資源)
本文介紹了枚舉類的基本概念,使用方法,以及底層實現原理。幫助你更好地使用枚舉類而且理解枚舉類的內部實現細節。git
具體代碼在個人GitHub中能夠找到程序員
https://github.com/h2pl/MyTech
喜歡的話麻煩點一下星哈謝謝。github
文章首發於個人我的博客:後端
https://h2pl.github.io/2018/0...
更多關於Java後端學習的內容請到個人CSDN博客上查看:數組
https://blog.csdn.net/a724888
枚舉(enum)類型是Java 5新增的特性,它是一種新的類型,容許用常量來表示特定的數據片段,並且所有都以類型安全的形式來表示。安全
## 初探枚舉類微信
在程序設計中,有時會用到由若干個有限數據元素組成的集合,如一週內的星期一到星期日七個數據元素組成的集合,由三種顏色紅、黃、綠組成的集合,一個工做班組內十個職工組成的集合等等,程序中某個變量取值僅限於集合中的元素。此時,可將這些數據集合定義爲枚舉類型。所以,枚舉類型是某類數據可能取值的集合,如一週內星期可能取值的集合爲:
{ Sun,Mon,Tue,Wed,Thu,Fri,Sat}
該集合可定義爲描述星期的枚舉類型,該枚舉類型共有七個元素,於是用枚舉類型定義的枚舉變量只能取集合中的某一元素值。因爲枚舉類型是導出數據類型,所以,必須先定義枚舉類型,而後再用枚舉類型定義枚舉型變量。
網絡
enum <枚舉類型名> { <枚舉元素表> }; 其中:關鍵詞enum表示定義的是枚舉類型,枚舉類型名由標識符組成,而枚舉元素表由枚舉元素或枚舉常量組成。例如: enum weekdays { Sun,Mon,Tue,Wed,Thu,Fri,Sat }; 定義了一個名爲 weekdays的枚舉類型,它包含七個元素:Sun、Mon、Tue、Wed、Thu、Fri、Sat。
在編譯器編譯程序時,給枚舉類型中的每個元素指定一個整型常量值(也稱爲序號值)。若枚舉類型定義中沒有指定元素的整型常量值,則整型常量值從0開始依次遞增,所以,weekdays枚舉類型的七個元素Sun、Mon、Tue、Wed、Thu、Fri、Sat對應的整型常量值分別爲0、一、二、三、四、五、6。
注意:在定義枚舉類型時,也可指定元素對應的整型常量值。
例如,描述邏輯值集合{TRUE、FALSE}的枚舉類型boolean可定義以下: enum boolean { TRUE=1 ,FALSE=0 }; 該定義規定:TRUE的值爲1,而FALSE的值爲0。 而描述顏色集合{red,blue,green,black,white,yellow}的枚舉類型colors可定義以下: enum colors {red=5,blue=1,green,black,white,yellow}; 該定義規定red爲5 ,blue爲1,其後元素值從2 開始遞增長1。green、black、white、yellow的值依次爲二、三、四、5。
此時,整數5將用於表示二種顏色red與yellow。一般兩個不一樣元素取相同的整數值是沒有意義的。枚舉類型的定義只是定義了一個新的數據類型,只有用枚舉類型定義枚舉變量才能使用這種數據類型。 多線程
## 枚舉類-語法
enum 與 class、interface 具備相同地位;
能夠繼承多個接口;
能夠擁有構造器、成員方法、成員變量;
1.2 枚舉類與普通類不一樣之處默認繼承 java.lang.Enum 類,因此不能繼承其餘父類;其中 java.lang.Enum 類實現了 java.lang.Serializable 和 java.lang.Comparable 接口;
使用 enum 定義,默認使用 final 修飾,所以不能派生子類;
構造器默認使用 private 修飾,且只能使用 private 修飾;
枚舉類全部實例必須在第一行給出,默認添加 public static final 修飾,不然沒法產生實例;
這部份內容參考https://blog.csdn.net/qq_2709...
public class 常量 { } enum Color { Red, Green, Blue, Yellow }
JDK1.6以前的switch語句只支持int,char,enum類型,使用枚舉,能讓咱們的代碼可讀性更強。
public static void showColor(Color color) { switch (color) { case Red: System.out.println(color); break; case Blue: System.out.println(color); break; case Yellow: System.out.println(color); break; case Green: System.out.println(color); break; } }
若是打算自定義本身的方法,那麼必須在enum實例序列的最後添加一個分號。並且 Java 要求必須先定義 enum 實例。
enum Color { //每一個顏色都是枚舉類的一個實例,而且構造方法要和枚舉類的格式相符合。 //若是實例後面有其餘內容,實例序列結束時要加分號。 Red("紅色", 1), Green("綠色", 2), Blue("藍色", 3), Yellow("黃色", 4); String name; int index; Color(String name, int index) { this.name = name; this.index = index; } public void showAllColors() { //values是Color實例的數組,在經過index和name能夠獲取對應的值。 for (Color color : Color.values()) { System.out.println(color.index + ":" + color.name); } } }
全部枚舉類都繼承自Enum類,因此能夠重寫該類的方法
下面給出一個toString()方法覆蓋的例子。
@Override public String toString() { return this.index + ":" + this.name; }
全部的枚舉都繼承自java.lang.Enum類。因爲Java 不支持多繼承,因此枚舉對象不能再繼承其餘類。
enum Color implements Print{ @Override public void print() { System.out.println(this.name); } }
搞個實現接口,來組織枚舉,簡單講,就是分類吧。若是大量使用枚舉的話,這麼幹,在寫代碼的時候,就很方便調用啦。
public class 用接口組織枚舉 { public static void main(String[] args) { Food cf = chineseFood.dumpling; Food jf = Food.JapaneseFood.fishpiece; for (Food food : chineseFood.values()) { System.out.println(food); } for (Food food : Food.JapaneseFood.values()) { System.out.println(food); } } } interface Food { enum JapaneseFood implements Food { suse, fishpiece } } enum chineseFood implements Food { dumpling, tofu }
java.util.EnumSet和java.util.EnumMap是兩個枚舉集合。EnumSet保證集合中的元素不重複;EnumMap中的 key是enum類型,而value則能夠是任意類型。
EnumSet在JDK中沒有找到實現類,這裏寫一個EnumMap的例子
public class 枚舉類集合 { public static void main(String[] args) { EnumMap<Color, String> map = new EnumMap<Color, String>(Color.class); map.put(Color.Blue, "Blue"); map.put(Color.Yellow, "Yellow"); map.put(Color.Red, "Red"); System.out.println(map.get(Color.Red)); } }
枚舉類型對象之間的值比較,是可使用==,直接來比較值,是否相等的,不是必須使用equals方法的喲。
由於枚舉類Enum已經重寫了equals方法
/** * Returns true if the specified object is equal to this * enum constant. * * @param other the object to be compared for equality with this object. * @return true if the specified object is equal to this * enum constant. */ public final boolean equals(Object other) { return this==other; }
這部分參考https://blog.csdn.net/mhmyqn/...
Java從JDK1.5開始支持枚舉,也就是說,Java一開始是不支持枚舉的,就像泛型同樣,都是JDK1.5才加入的新特性。一般一個特性若是在一開始沒有提供,在語言發展後期才添加,會遇到一個問題,就是向後兼容性的問題。像Java在1.5中引入的不少特性,爲了向後兼容,編譯器會幫咱們寫的源代碼作不少事情,好比泛型爲何會擦除類型,爲何會生成橋接方法,foreach迭代,自動裝箱/拆箱等,這有個術語叫「語法糖」,而編譯器的特殊處理叫「解語法糖」。那麼像枚舉也是在JDK1.5中才引入的,又是怎麼實現的呢?
Java在1.5中添加了java.lang.Enum抽象類,它是全部枚舉類型基類。提供了一些基礎屬性和基礎方法。同時,對把枚舉用做Set和Map也提供了支持,即java.util.EnumSet和java.util.EnumMap。
接下來定義一個簡單的枚舉類
public enum Day { MONDAY { @Override void say() { System.out.println("MONDAY"); } } , TUESDAY { @Override void say() { System.out.println("TUESDAY"); } }, FRIDAY("work"){ @Override void say() { System.out.println("FRIDAY"); } }, SUNDAY("free"){ @Override void say() { System.out.println("SUNDAY"); } }; String work; //沒有構造參數時,每一個實例能夠看作常量。 //使用構造參數時,每一個實例都會變得不同,能夠看作不一樣的類型,因此編譯後會生成實例個數對應的class。 private Day(String work) { this.work = work; } private Day() { } //枚舉實例必須實現枚舉類中的抽象方法 abstract void say (); }
反編譯結果
D:\MyTech\out\production\MyTech\com\javase\枚舉類>javap Day.class Compiled from "Day.java" public abstract class com.javase.枚舉類.Day extends java.lang.Enum<com.javase.枚舉類.Day> { public static final com.javase.枚舉類.Day MONDAY; public static final com.javase.枚舉類.Day TUESDAY; public static final com.javase.枚舉類.Day FRIDAY; public static final com.javase.枚舉類.Day SUNDAY; java.lang.String work; public static com.javase.枚舉類.Day[] values(); public static com.javase.枚舉類.Day valueOf(java.lang.String); abstract void say(); com.javase.枚舉類.Day(java.lang.String, int, com.javase.枚舉類.Day$1); com.javase.枚舉類.Day(java.lang.String, int, java.lang.String, com.javase.枚舉類.Day$1); static {}; }
能夠看到,一個枚舉在通過編譯器編譯事後,變成了一個抽象類,它繼承了java.lang.Enum;而枚舉中定義的枚舉常量,變成了相應的public static final屬性,並且其類型就抽象類的類型,名字就是枚舉常量的名字.同時咱們能夠在Operator.class的相同路徑下看到四個內部類的.class文件com/mikan/Day$1.class、com/mikan/Day$2.class、com/mikan/Day$3.class、com/mikan/Day$4.class,也就是說這四個命名字段分別使用了內部類來實現的;同時添加了兩個方法values()和valueOf(String);咱們定義的構造方法原本只有一個參數,但卻變成了三個參數;同時還生成了一個靜態代碼塊。這些具體的內容接下來仔細看看。
下面分析一下字節碼中的各部分,其中:
InnerClasses: static #23; //class com/javase/枚舉類/Day$4 static #18; //class com/javase/枚舉類/Day$3 static #14; //class com/javase/枚舉類/Day$2 static #10; //class com/javase/枚舉類/Day$1
從中能夠看到它有4個內部類,這四個內部類的詳細信息後面會分析。
static {}; descriptor: ()V flags: ACC_STATIC Code: stack=5, locals=0, args_size=0 0: new #10 // class com/javase/枚舉類/Day$1 3: dup 4: ldc #11 // String MONDAY 6: iconst_0 7: invokespecial #12 // Method com/javase/枚舉類/Day$1."<init>":(Ljava/lang/String;I)V 10: putstatic #13 // Field MONDAY:Lcom/javase/枚舉類/Day; 13: new #14 // class com/javase/枚舉類/Day$2 16: dup 17: ldc #15 // String TUESDAY 19: iconst_1 20: invokespecial #16 // Method com/javase/枚舉類/Day$2."<init>":(Ljava/lang/String;I)V //後面相似,這裏省略 }
其實編譯器生成的這個靜態代碼塊作了以下工做:分別設置生成的四個公共靜態常量字段的值,同時編譯器還生成了一個靜態字段$VALUES,保存的是枚舉類型定義的全部枚舉常量
編譯器添加的values方法:
public static com.javase.Day[] values(); flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #2 // Field $VALUES:[Lcom/javase/Day; 3: invokevirtual #3 // Method "[Lcom/mikan/Day;".clone:()Ljava/lang/Object; 6: checkcast #4 // class "[Lcom/javase/Day;" 9: areturn 這個方法是一個公共的靜態方法,因此咱們能夠直接調用該方法(Day.values()),返回這個枚舉值的數組,另外,這個方法的實現是,克隆在靜態代碼塊中初始化的$VALUES字段的值,並把類型強轉成Day[]類型返回。
造方法爲何增長了兩個參數?
有一個問題,構造方法咱們明明只定義了一個參數,爲何生成的構造方法是三個參數呢?
從Enum類中咱們能夠看到,爲每一個枚舉都定義了兩個屬性,name和ordinal,name表示咱們定義的枚舉常量的名稱,如FRIDAY、TUESDAY,而ordinal是一個順序號,根據定義的順序分別賦予一個整形值,從0開始。在枚舉常量初始化時,會自動爲初始化這兩個字段,設置相應的值,因此纔在構造方法中添加了兩個參數。即: 另外三個枚舉常量生成的內部類基本上差很少,這裏就不重複說明了。
咱們能夠從Enum類的代碼中看到,定義的name和ordinal屬性都是final的,並且大部分方法也都是final的,特別是clone、readObject、writeObject這三個方法,這三個方法和枚舉經過靜態代碼塊來進行初始化一塊兒。它保證了枚舉類型的不可變性,不能經過克隆,不能經過序列化和反序列化來複制枚舉,這能保證一個枚舉常量只是一個實例,便是單例的,因此在effective java中推薦使用枚舉來實現單例。
枚舉本質上是經過普通的類來實現的,只是編譯器爲咱們進行了處理。每一個枚舉類型都繼承自java.lang.Enum,並自動添加了values和valueOf方法。
而每一個枚舉常量是一個靜態常量字段,使用內部類實現,該內部類繼承了枚舉類。全部枚舉常量都經過靜態代碼塊來進行初始化,即在類加載期間就初始化。
另外經過把clone、readObject、writeObject這三個方法定義爲final的,同時實現是拋出相應的異常。這樣保證了每一個枚舉類型及枚舉常量都是不可變的。能夠利用枚舉的這兩個特性來實現線程安全的單例。