java基础(十一) 枚举类型
枚舉類型Enum的簡介
1.什么是枚舉類型
枚舉類型: 就是由一組具有名的值的有限集合組成新的類型。(即新的類)。
好像還是不懂,別急,咱們先來看一下 為什么要引入枚舉類型
在沒有引入枚舉類型前,當(dāng)我們想要維護(hù)一組 常量集合時(shí),我們是這樣做的,看下面的例子:
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; }當(dāng)我們有枚舉類型后,便可以簡寫成:
//枚舉類型 public enum FavouriteColor {//枚舉成員RED,GREEN,BLACK,BLUE,WHITE,BROWN }是不是很簡單,很清晰。這樣就可以省掉大量重復(fù)的代碼,使得代碼更加易于維護(hù)。
現(xiàn)在有點(diǎn)明白枚舉類型的定義了吧!在說的再仔細(xì)一點(diǎn),就是 使用關(guān)鍵字enum來用 一組由常量組成的有限集合 來創(chuàng)建一個(gè)新的class類 。至于新的class類型,請繼續(xù)往下看。
二、 深入分析枚舉的特性與實(shí)現(xiàn)原理
??上面僅僅簡單地介紹了枚舉類型的最簡單的用法,下面我們將逐步深入,掌握枚舉類型的復(fù)雜的用法,以及其原理。
1. 枚舉成員
??上面的枚舉類FavouriteColor里面的成員便都是枚舉成員,換句話說,枚舉成員 就是枚舉類中,沒有任何類型修飾,只有變量名,也不能賦值的成員。
到這里還是對枚舉成員很疑惑,我們先將上面的例子進(jìn)行反編譯一下:
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; }??從反編譯的結(jié)果可以看出,枚舉成員都被處理成 public static final 的靜態(tài)枚舉常量。即上面例子的枚舉成員都是 枚舉類FavouriteColor 的實(shí)例。
2. 為枚舉類型添加方法、構(gòu)造器、非枚舉的成員
枚舉類型在添加方法、構(gòu)造器、非枚舉成員時(shí),與普通類是沒有多大的區(qū)別,除了以下幾個(gè)限制:
- 枚舉成員必須是最先聲明,且只能用一行聲明(相互間以逗號隔開,分號結(jié)束聲明)。
- 構(gòu)造器的訪問權(quán)限只能是private(可以不寫,默認(rèn)強(qiáng)制是private),不能是public、protected。
可以看出,我們其實(shí)是可以使用Eunm類型做很多事情,雖然,我們一般只使用普通的枚舉類型。
仔細(xì)看一下所有的枚舉成員,我們會(huì)發(fā)現(xiàn)GREEN(2), BLACK(3) 這兩個(gè)枚舉成員有點(diǎn)奇怪!其實(shí)也很簡答,前面說了,枚舉成員其實(shí)就是枚舉類型的實(shí)例,所以,GREEN(2), BLACK(3) 就是指明了用帶參構(gòu)造器,并傳入?yún)?shù),即可以理解成 FavouriteColor GREEN = new FavouriteColor(2)。其他幾個(gè)枚舉類型則表示使用無參構(gòu)造器來創(chuàng)建對象。( 事實(shí)上,編譯器會(huì)重新創(chuàng)建每個(gè)構(gòu)造器,為每個(gè)構(gòu)造器多加兩個(gè)參數(shù))。
3. 包含抽象方法的枚舉類型
枚舉類型也是允許包含抽象方法的(除了幾個(gè)小限制外,枚舉類幾乎與普通類一樣),那么包含抽象方法的枚舉類型的枚舉成員是怎么樣的,編譯器又是怎么處理的?
我們知道,上面的例子 FavouriteColor 類經(jīng)過反編譯后得到的類是一個(gè)繼承了Enum的final類:
public final class FavouriteColor extends Enum那么包含抽象方法的枚舉類型是不是也是被編譯器處理成 final類,如果是這樣,那有怎么被子類繼承呢? 還是處理成 abstract 類呢?
我們看個(gè)包含抽象方法的枚舉類的例子,Fruit 類中有三種水果,希望能為每種水果輸出對應(yīng)的信息:
public enum Frutit {APPLE {@Overridepublic void printFruitInfo() {System.out.println("This is apple");}},BANANA {@Overridepublic void printFruitInfo() {System.out.println("This is apple");}},WATERMELON {@Overridepublic void printFruitInfo() {System.out.println("This is apple");}};//抽象方法public abstract void printFruitInfo();public static void main(String[] arg) {Frutit.APPLE.printFruitInfo();} }運(yùn)行結(jié)果:
This is apple
對于上面的枚舉成員的形式也很容易理解,因?yàn)槊杜e成員是一個(gè)枚舉類型的實(shí)例,上面的這種形式就是一種匿名內(nèi)部類的形式,即每個(gè)枚舉成員的創(chuàng)建可以理解成:
BANANA = new Frutit("BANANA", 1) {//此構(gòu)造器是編譯器生成的,下面會(huì)說public void printFruitInfo() {//匿名內(nèi)部類的抽象方法實(shí)現(xiàn)。System.out.println("This is apple");}};事實(shí)上,編譯器確實(shí)就是這樣處理的,即上面的例子中,創(chuàng)建了三個(gè)匿名內(nèi)部類,同時(shí)也會(huì)多創(chuàng)建三個(gè)class文件.
最后,我們反編譯一下fruit類,看fruit類的定義:
public abstract class Frutit extends EnumFruit類被處理成抽象類,所以可以說,枚舉類型經(jīng)過編譯器的處理,含抽象方法的將被處理成抽象類,否則處理成final類。
4. 枚舉類型的父類 – Enum
??每一個(gè)枚舉類型都繼承了Enum,所以是很有必要來了解一下Enum;
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {//枚舉成員的名稱 private final String name; //枚舉成員的順序,是按照定義的順序,從0開始 private final int ordinal;//構(gòu)造方法 protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}public final int ordinal() {//返回枚舉常量的序數(shù)return ordinal;}}public final String name() {//返回此枚舉常量的名稱,在其枚舉聲明中對其進(jìn)行聲明。return name;}public final boolean equals(Object other) {return this==other;//比較地址}public final int hashCode() {return super.hashCode();}public final int compareTo(E o) {//返回枚舉常量的序數(shù)//是按照次序 ordinal來比較的 }public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { }public String toString() {return name;}以上都是一些可能會(huì)用到的方法,我們從上面可以發(fā)現(xiàn)兩個(gè)有趣的地方:
- Enum類實(shí)現(xiàn)了 Serializable 接口,也就是說可以枚舉類型可以進(jìn)行序列化。
- Enum的幾乎所有方法都是final方法,也就是說,枚舉類型只能重寫toString()方法,其他方法不能重寫,連hashcode()、equal()等方法也不行。
5. 真正掌握枚舉類型的原理
??上面說了這么多,都是片面地、簡單地理解了枚舉類型,但還沒有完全掌握枚舉類型的本質(zhì),有了上面的基礎(chǔ),我們將如魚得水。
想要真正理解枚舉類型的本質(zhì),就得了解編譯器是如何處理枚舉類型的,也就是老辦法 – 反編譯。這次看一個(gè)完整的反編譯代碼,先看一個(gè)例子:
public enum Fruit {APPLE ,BANANA ,WATERMELON ;private int value;private Fruit() {//默認(rèn)構(gòu)造器this.value = 0; } private Fruit(int value) {//帶參數(shù)的構(gòu)造器this.value = value;} }反編譯的結(jié)果:
public final class Fruit extends Enum {//3個(gè)枚舉成員實(shí)例public static final Fruit APPLE;public static final Fruit BANANA;public static final Fruit WATERMELON;private int value;//普通變量private static final Fruit ENUM$VALUES[];//存儲(chǔ)枚舉常量的枚舉數(shù)組static {//靜態(tài)域,初始化枚舉常量,枚舉數(shù)組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) {//編譯器改造了默認(rèn)構(gòu)造器super(s, i);value = 0;}private Fruit(String s, int i, int value) {//編譯器改造了帶參數(shù)的構(gòu)造器super(s, i);this.value = value;}public static Fruit[] values() {//編譯器添加了靜態(tài)方法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) {//編譯器添加了靜態(tài)方法valueOf()return (Fruit) Enum.valueOf(Test_2018_1_16 / Fruit, s);} }??從反編譯的結(jié)果可以看出,編譯器為我們創(chuàng)建出來的枚舉類做了很多工作:
- 對枚舉成員的處理
??編譯器對所有的枚舉成員處理成public static final的枚舉常量,并在靜態(tài)域中進(jìn)行初始化。
- 構(gòu)造器
??編譯器重新定義了構(gòu)造器,不僅為每個(gè)構(gòu)造器都增加了兩個(gè)參數(shù),還添加父類了的構(gòu)造方法調(diào)用。
- 添加了兩個(gè)類方法
?? 編譯器為枚舉類添加了 values() 和 valueOf()。values()方法返回一個(gè)枚舉類型的數(shù)組,可用于遍歷枚舉類型。valueOf()方法也是新增的,而且是重載了父類的valueOf()方法
注意了: 正因?yàn)槊杜e類型的真正構(gòu)造器是再編譯時(shí)才生成的,所以我們沒法創(chuàng)建枚舉類型的實(shí)例,以及繼承擴(kuò)展枚舉類型(即使是被處理成abstract類)。枚舉類型的實(shí)例只能由編譯器來處理創(chuàng)建
三、 枚舉類型的使用
1. switch
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;}2. 實(shí)現(xiàn)接口
??實(shí)現(xiàn)接口就不多說了。枚舉類型繼承了Enum類,所以不能再繼承其他類,但可以實(shí)現(xiàn)接口。
3. 使用接口組織枚舉
前面說了,枚舉類型是無法被子類繼承擴(kuò)展的,這就造成無法滿足以下兩種情況的需求:
- 希望擴(kuò)展原來的枚舉類型中的元素;
- 希望使用子類對枚舉類型中的元素進(jìn)行分組;
看一個(gè)例子:對食物進(jìn)行分類,大類是 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作為一個(gè)大類,3種枚舉類型做為接口的子類;Food管理著這些枚舉類型。對于枚舉而言,實(shí)現(xiàn)接口是使其子類化的唯一辦法,所以嵌套在Food中的每個(gè)枚舉類都實(shí)現(xiàn)了Food接口。從而“所有這東西都是某種類型的Food”。
Food food = Food.Coffee.ESPERSSO;//ESPERSSO不僅是coffee,也屬于大類Food,達(dá)到分類的效果4. 使用枚舉來實(shí)現(xiàn)單例模式
對于序列化和反序列化,因?yàn)槊恳粋€(gè)枚舉類型和枚舉變量在JVM中都是唯一的,即Java在序列化和反序列化枚舉時(shí)做了特殊的規(guī)定,枚舉的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被編譯器禁用的,因此,對于枚舉單例,是不存在實(shí)現(xiàn)序列化接口后調(diào)用readObject會(huì)破壞單例的問題。所以,枚舉單例是單利模式的最佳實(shí)現(xiàn)方式。
public enum EnumSingletonDemo {SINGLETON;//其他方法、成員等public int otherMethod() {return 0;} }單例的使用方式:
int a = EnumSingletonDemo.SINGLETON.otherMethod();四、EnumSet、EnumMap
??此處只是簡單地介紹這兩個(gè)類的使用,并不深入分析其實(shí)現(xiàn)原理。
1、EnumSet
EnumSet是一個(gè)抽象類,繼承了AbstractSet類,其本質(zhì)上就是一個(gè)Set**。只不過,Enumset是要與枚舉類型一起使用的專用 Set 實(shí)現(xiàn)。枚舉 set 中所有鍵都必須來自單個(gè)枚舉類型,該枚舉類型在創(chuàng)建 set 時(shí)顯式或隱式地指定**。
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>盡管JDK沒有提供EnumSet的實(shí)現(xiàn)子類,但是EnumSet新增的方法都是static方法,而且這些方法都是用來創(chuàng)建一個(gè)EnumSet的對象。因此可以看做是一個(gè)對枚舉中的元素進(jìn)行操作的Set,而且性能也很高。看下面的例子:
public static void main(String[] args) {//創(chuàng)建對象,并指定EnumSet存儲(chǔ)的枚舉類型EnumSet<FavouriteColor> set = EnumSet.allOf(FavouriteColor.class);//移除枚舉元素set.remove(FavouriteColor.BLACK);set.remove(FavouriteColor.BLUE);for(FavouriteColor color : set) {//遍歷setSystem.out.println(color);} }運(yùn)行結(jié)果:
RED
GREEN
WHITE
BROWN
EnumSet不支持同步訪問。實(shí)現(xiàn)線程安全的方式是:
Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));2. EnumMap
EnumMap是一個(gè)類,同樣也是與枚舉類型鍵一起使用的專用 Map 實(shí)現(xiàn)。枚舉映射中所有鍵都必須來自單個(gè)枚舉類型,該枚舉類型在創(chuàng)建映射時(shí)顯式或隱式地指定。枚舉映射在內(nèi)部表示為數(shù)組。此表示形式非常緊湊且高效。
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>(...));總結(jié):
- 枚舉類型繼承于Enum類,所以只能用實(shí)現(xiàn)接口,不能再繼承其他類。
- 枚舉類型會(huì)編譯器處理成 抽象類(含抽象方法)或 final類。
- 枚舉成員都是public static final 的枚舉實(shí)例常量。枚舉成員必須是最先聲明,且只能聲明一行(逗號隔開,分號結(jié)束)。
- 構(gòu)造方法必須是 private,如果定義了有參的構(gòu)造器,就要注意枚舉成員的聲明。沒有定義構(gòu)造方法時(shí),編譯器為枚舉類自動(dòng)添加的是一個(gè)帶兩個(gè)參數(shù)的構(gòu)造方法,并不是無參構(gòu)造器。
- 編譯器會(huì)為枚舉類添加 values() 和 valueOf()兩個(gè)方法。
- 沒有抽象方法的枚舉類,被編譯器處理成 final 類。如果是包含抽象方法的枚舉類則被處理成抽象abstract類。
- Enum實(shí)現(xiàn)了Serializable接口,并且?guī)缀跛蟹椒ǘ际?final方法
作者:jinggod
出處:http://www.cnblogs.com/jinggod/p/8503281.html
總結(jié)
以上是生活随笔為你收集整理的java基础(十一) 枚举类型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java基础(十) 数组类型
- 下一篇: java网络编程(五)