枚舉類型: 就是由一組具備名的值的有限集合組成新的類型。(即新的類)。android
好像仍是不懂,別急,我們先來看一下 爲何要引入枚舉類型數組
在沒有引入枚舉類型前,當咱們想要維護一組 常量集合時,咱們是這樣作的,看下面的例子:安全
class FavouriteColor_class{ public static final int RED = 1; public static final int BLACK = 3; public static final int GREEN = 2; public static final int BLUE = 4; public static final int WHITE = 5; public static final int BROWN = 6;}複製代碼
當咱們有枚舉類型後,即可以簡寫成:bash
//枚舉類型public enum FavouriteColor { //枚舉成員 RED,GREEN,BLACK,BLUE,WHITE,BROWN}複製代碼
是否是很簡單,很清晰。這樣就能夠省掉大量重複的代碼,使得代碼更加易於維護。cookie
如今有點明白枚舉類型的定義了吧!在說的再仔細一點,就是 使用關鍵字enum來用 一組由常量組成的有限集合 來建立一個新的class類 。至於新的class類型,請繼續往下看。app
上面僅僅簡單地介紹了枚舉類型的最簡單的用法,下面咱們將逐步深刻,掌握枚舉類型的複雜的用法,以及其原理。ide
上面的枚舉類FavouriteColor裏面的成員便都是枚舉成員,換句話說,枚舉成員 就是枚舉類中,沒有任何類型修飾,只有變量名,也不能賦值的成員。性能
到這裏仍是對枚舉成員很疑惑,咱們先將上面的例子進行反編譯一下:ui
public final class FavouriteColor extends Enum { public static final FavouriteColor RED; public static final FavouriteColor GREEN; public static final FavouriteColor BLACK; public static final FavouriteColor BLUE; public static final FavouriteColor WHITE; public static final FavouriteColor BROWN;}複製代碼
從反編譯的結果能夠看出,枚舉成員都被處理成 public static final
的靜態枚舉常量。即上面例子的枚舉成員都是 枚舉類FavouriteColor
的實例。this
枚舉類型在添加方法、構造器、非枚舉成員時,與普通類是沒有多大的區別,除了如下幾個限制:
枚舉成員必須是最早聲明,且只能用一行聲明(相互間以逗號隔開,分號結束聲明)。
構造器的訪問權限只能是private(能夠不寫,默認強制是private),不能是public、protected。
public enum FavouriteColor { //枚舉成員 RED, GREEN(2), BLACK(3), BLUE, WHITE, BROWN;// 必需要有分號 // 非枚舉類型的成員 private int colorValue; public int aa; // 靜態常量也能夠 public static final int cc = 2; //無參構造器 private FavouriteColor() { } //有參構造器 FavouriteColor(int colorValue) { this.colorValue = colorValue; } //方法 public void print() { System.out.println(cc); }}複製代碼
能夠看出,咱們實際上是可使用Eunm類型作不少事情,雖然,咱們通常只使用普通的枚舉類型。
仔細看一下全部的枚舉成員,咱們會發現GREEN(2)
, BLACK(3)
這兩個枚舉成員有點奇怪!其實也很簡答,前面說了,枚舉成員其實就是枚舉類型的實例,因此,GREEN(2)
, BLACK(3)
就是指明瞭用帶參構造器,並傳入參數,便可以理解成 FavouriteColor GREEN = new FavouriteColor(2)
。其餘幾個枚舉類型則表示使用無參構造器來建立對象。( 事實上,編譯器會從新建立每一個構造器,爲每一個構造器多加兩個參數)。
枚舉類型也是容許包含抽象方法的(除了幾個小限制外,枚舉類幾乎與普通類同樣),那麼包含抽象方法的枚舉類型的枚舉成員是怎麼樣的,編譯器又是怎麼處理的?
咱們知道,上面的例子 FavouriteColor 類通過反編譯後獲得的類是一個繼承了Enum的final類:
public final class FavouriteColor extends Enum 複製代碼
那麼包含抽象方法的枚舉類型是否是也是被編譯器處理成 final類,若是是這樣,那有怎麼被子類繼承呢? 仍是處理成 abstract 類呢?
咱們看個包含抽象方法的枚舉類的例子,Fruit 類中有三種水果,但願能爲每種水果輸出對應的信息:
public enum Frutit { APPLE { @Override public void printFruitInfo() { System.out.println("This is apple"); } },BANANA { @Override public void printFruitInfo() { System.out.println("This is apple"); } },WATERMELON { @Override public void printFruitInfo() { System.out.println("This is apple"); } }; //抽象方法 public abstract void printFruitInfo(); public static void main(String[] arg) { Frutit.APPLE.printFruitInfo(); }}複製代碼
運行結果:
This is apple
對於上面的枚舉成員的形式也很容易理解,由於枚舉成員是一個枚舉類型的實例,上面的這種形式就是一種匿名內部類的形式,即每一個枚舉成員的建立能夠理解成:
BANANA = new Frutit("BANANA", 1) {//此構造器是編譯器生成的,下面會說 public void printFruitInfo() {//匿名內部類的抽象方法實現。 System.out.println("This is apple"); } };複製代碼
事實上,編譯器確實就是這樣處理的,即上面的例子中,建立了三個匿名內部類,同時也會多建立三個class文件.
最後,咱們反編譯一下fruit類,看fruit類的定義:
public abstract class Frutit extends Enum複製代碼
Fruit類被處理成抽象類,因此能夠說,枚舉類型通過編譯器的處理,含抽象方法的將被處理成抽象類,不然處理成final類。
每個枚舉類型都繼承了Enum,因此是頗有必要來了解一下Enum;
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {//枚舉成員的名稱private final String name;//枚舉成員的順序,是按照定義的順序,從0開始private final int ordinal;//構造方法protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } public final int ordinal() {//返回枚舉常量的序數 return ordinal; } } public final String name() {//返回此枚舉常量的名稱,在其枚舉聲明中對其進行聲明。 return name; } public final boolean equals(Object other) { return this==other;//比較地址 }public final int hashCode() { return super.hashCode(); }public final int compareTo(E o) {//返回枚舉常量的序數 //是按照次序 ordinal來比較的} public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { } public String toString() { return name; }複製代碼
以上都是一些可能會用到的方法,咱們從上面能夠發現兩個有趣的地方:
Enum類實現了 Serializable 接口,也就是說能夠枚舉類型能夠進行序列化。
Enum的幾乎全部方法都是final方法,也就是說,枚舉類型只能重寫toString()方法,其餘方法不能重寫,連hashcode()、equal()等方法也不行。
上面說了這麼多,都是片面地、簡單地理解了枚舉類型,但尚未徹底掌握枚舉類型的本質,有了上面的基礎,咱們將如魚得水。
想要真正理解枚舉類型的本質,就得了解編譯器是如何處理枚舉類型的,也就是老辦法 -- 反編譯。此次看一個完整的反編譯代碼,先看一個例子:
public enum Fruit { APPLE ,BANANA ,WATERMELON ; private int value; private Fruit() {//默認構造器 this.value = 0; } private Fruit(int value) {//帶參數的構造器 this.value = value; }}複製代碼
反編譯的結果:
public final class Fruit extends Enum { //3個枚舉成員實例 public static final Fruit APPLE; public static final Fruit BANANA; public static final Fruit WATERMELON; private int value;//普通變量 private static final Fruit ENUM$VALUES[];//存儲枚舉常量的枚舉數組 static {//靜態域,初始化枚舉常量,枚舉數組 APPLE = new Fruit("APPLE", 0); BANANA = new Fruit("BANANA", 1); WATERMELON = new Fruit("WATERMELON", 2); ENUM$VALUES = (new Fruit[]{APPLE, BANANA, WATERMELON}); } private Fruit(String s, int i) {//編譯器改造了默認構造器 super(s, i); value = 0; } private Fruit(String s, int i, int value) {//編譯器改造了帶參數的構造器 super(s, i); this.value = value; } public static Fruit[] values() {//編譯器添加了靜態方法values() Fruit afruit[]; int i; Fruit afruit1[]; System.arraycopy(afruit = ENUM$VALUES, 0, afruit1 = new Fruit[i = afruit.length], 0, i); return afruit1; } public static Fruit valueOf(String s) {//編譯器添加了靜態方法valueOf() return (Fruit) Enum.valueOf(Test_2018_1_16 / Fruit, s); }}複製代碼
從反編譯的結果能夠看出,編譯器爲咱們建立出來的枚舉類作了不少工做:
- 對枚舉成員的處理
編譯器對全部的枚舉成員處理成public static final
的枚舉常量,並在靜態域中進行初始化。
- 構造器
編譯器從新定義了構造器,不只爲每一個構造器都增長了兩個參數,還添加父類了的構造方法調用。
- 添加了兩個類方法
編譯器爲枚舉類添加了 values()
和 valueOf()
。values()
方法返回一個枚舉類型的數組,可用於遍歷枚舉類型。valueOf()
方法也是新增的,並且是重載了父類的valueOf()
方法
注意了: 正由於枚舉類型的真正構造器是再編譯時才生成的,因此咱們無法建立枚舉類型的實例,以及繼承擴展枚舉類型(即便是被處理成abstract類)。枚舉類型的實例只能由編譯器來處理建立
Fruit fruit = Fruit.APPLE; switch (fruit) { case APPLE: System.out.println("APPLE"); break; case BANANA: System.out.println("BANANA"); break; case WATERMELON: System.out.println("WATERMELON"); break; }複製代碼
實現接口就很少說了。枚舉類型繼承了Enum類,因此不能再繼承其餘類,但能夠實現接口。
前面說了,枚舉類型是沒法被子類繼承擴展的,這就形成沒法知足如下兩種狀況的需求:
但願擴展原來的枚舉類型中的元素;
但願使用子類對枚舉類型中的元素進行分組;
看一個例子:對食物進行分類,大類是 Food,Food下面有好幾種食物類別,類別上纔是具體的食物;
public interface Food { enum Appetizer implements Food { SALAD, SOUP, SPRING_ROLLS } enum Coffee implements Food { BLACK_COFFEE, DECAF_COFFEE, ESPERSSO, TEA; } enum Dessert implements Food { FRUIT, GELATO, TIRAMISU; } }複製代碼
接口Food做爲一個大類,3種枚舉類型作爲接口的子類;Food管理着這些枚舉類型。對於枚舉而言,實現接口是使其子類化的惟一辦法,因此嵌套在Food中的每一個枚舉類都實現了Food接口。從而「全部這東西都是某種類型的Food」。
Food food = Food.Coffee.ESPERSSO;//ESPERSSO不只是coffee,也屬於大類Food,達到分類的效果複製代碼
對於序列化和反序列化,由於每個枚舉類型和枚舉變量在JVM中都是惟一的,即Java在序列化和反序列化枚舉時作了特殊的規定,枚舉的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被編譯器禁用的,所以,對於枚舉單例,是不存在實現序列化接口後調用readObject會破壞單例的問題。因此,枚舉單例是單利模式的最佳實現方式。
public enum EnumSingletonDemo { SINGLETON; //其餘方法、成員等 public int otherMethod() { return 0; }}複製代碼
單例的使用方式:
int a = EnumSingletonDemo.SINGLETON.otherMethod();複製代碼
此處只是簡單地介紹這兩個類的使用,並不深刻分析其實現原理。
EnumSet是一個抽象類,繼承了AbstractSet
類,其本質上就是一個Set。只不過,Enumset是要與枚舉類型一塊兒使用的專用 Set 實現。枚舉 set 中全部鍵都必須來自單個枚舉類型,該枚舉類型在建立 set 時顯式或隱式地指定。
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>複製代碼
儘管JDK沒有提供EnumSet
的實現子類,可是EnumSet
新增的方法都是static方法,並且這些方法都是用來建立一個EnumSet的對象。所以能夠看作是一個對枚舉中的元素進行操做的Set,並且性能也很高。看下面的例子:
public static void main(String[] args) { //建立對象,並指定EnumSet存儲的枚舉類型 EnumSet<FavouriteColor> set = EnumSet.allOf(FavouriteColor.class); //移除枚舉元素 set.remove(FavouriteColor.BLACK); set.remove(FavouriteColor.BLUE); for(FavouriteColor color : set) {//遍歷set System.out.println(color); }}複製代碼
運行結果:
RED
GREEN
WHITE
BROWN
EnumSet不支持同步訪問。實現線程安全的方式是:
Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));複製代碼
EnumMap
是一個類,一樣也是與枚舉類型鍵一塊兒使用的專用 Map 實現。枚舉映射中全部鍵都必須來自單個枚舉類型,該枚舉類型在建立映射時顯式或隱式地指定。枚舉映射在內部表示爲數組。此表示形式很是緊湊且高效。
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>複製代碼
簡單使用的例子:
public static void main(String[] args) { EnumMap< FavouriteColor,Integer> map = new EnumMap<>(FavouriteColor.class); map.put(FavouriteColor.BLACK,1 ); map.put(FavouriteColor.BLUE, 2); map.put(FavouriteColor.BROWN, 3); System.out.println(map.get(FavouriteColor.BLACK));}複製代碼
一樣,防止意外的同步操做:
Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));複製代碼
枚舉類型繼承於Enum類,因此只能用實現接口,不能再繼承其餘類。
枚舉類型會編譯器處理成 抽象類(含抽象方法)或 final類。
枚舉成員都是public static final 的枚舉實例常量。枚舉成員必須是最早聲明,且只能聲明一行(逗號隔開,分號結束)。
構造方法必須是 private,若是定義了有參的構造器,就要注意枚舉成員的聲明。沒有定義構造方法時,編譯器爲枚舉類自動添加的是一個帶兩個參數的構造方法,並非無參構造器。
編譯器會爲枚舉類添加 values() 和 valueOf()兩個方法。
沒有抽象方法的枚舉類,被編譯器處理成 final 類。若是是包含抽象方法的枚舉類則被處理成抽象abstract類。
Enum實現了Serializable接口,而且幾乎全部方法都是 final方法