java.lang包—枚举类Enum
原文作者:山高我為
原文地址:java enum的用法詳解
目錄
一、enum關(guān)鍵字
二、Enum類(lèi)源碼
三、疑問(wèn)
四、Enum常見(jiàn)用法
一、enum關(guān)鍵字
enum關(guān)鍵字是在Java1.5也就是Java SE5之后引入的一個(gè)新特性:它通過(guò)關(guān)鍵字enum來(lái)定義一個(gè)枚舉類(lèi),這個(gè)被定義的枚舉類(lèi)繼承Enum類(lèi),這個(gè)枚舉類(lèi)算是一種特殊類(lèi),它同樣能像其他普通類(lèi)一樣擁有構(gòu)造器、方法,也能夠?qū)崿F(xiàn)接口,但是它不能再繼承其他別的類(lèi),因?yàn)樗闹苯痈割?lèi)是Enum類(lèi),并且因?yàn)樗J(rèn)的修飾符有final的存在,因此它無(wú)法直接派生出其他子類(lèi),除非將其使用abstract修飾。
按照《Java編程思想》中的原話來(lái)說(shuō):關(guān)鍵字enum可以將一組具名的值的有限集合創(chuàng)建為一種新的類(lèi)型,而這些具名的值可以作為常規(guī)的程序組件來(lái)使用。
在枚舉類(lèi)出現(xiàn)之前Java是將常量放在接口或是放在普通類(lèi)當(dāng)中,然后使用public、static、final去修飾定義的常量,如下兩個(gè)例子:
public interface Constants2 {public static final int CONSTANT_1 = 1;public static final int CONSTANT_2 = 2;public static final int CONSTANT_3 = 3; }public class Constants {public static final int CONSTANT_1 = 1;public static final int CONSTANT_2 = 2;public static final int CONSTANT_3 = 3; }在枚舉類(lèi)型出現(xiàn)之后,就可以使用枚舉類(lèi)型來(lái)定義常量,這些枚舉類(lèi)型成員_1、_2、_3都默認(rèn)被public、static、final修飾,語(yǔ)法如下:
public enum Constants {CONSTANT_1,CONSTANT_2,CONSTANT_3 }?
但是Java枚舉類(lèi)型輸出其常量的時(shí)候不像C?/C++的枚舉那樣是數(shù)字,輸出的是其常量名,如果需要輸出其類(lèi)型成員聲明時(shí)數(shù)字次序的話,需要調(diào)用ordinal()方法:
public enum Singleton2 {SHERLOCK,WASTON; }class Main{public static void main(String[] args) {System.out.println(Singleton2.SHERLOCK);System.out.println(Singleton2.WASTON);System.out.println(Singleton2.SHERLOCK.ordinal());System.out.println(Singleton2.WASTON.ordinal());} }//輸出結(jié)果: //SHERLOCK //WASTON //0 //1?
二、Enum類(lèi)源碼
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {/*** 枚舉常量的名稱* 使用toString方法訪問(wèn)此字段。*/private final String name;/*** 返回此枚舉常量的名稱,與其枚舉聲明中聲明的完全相同.* 大多數(shù)程序員應(yīng)優(yōu)先使用toString方法,因?yàn)閠oString方法可能會(huì)返回一個(gè)更加友好的名稱。* 此方法主要用于特殊情況,其中正確性取決于獲取確切名稱,不會(huì)因發(fā)行版本而異。*/public final String name() {return name;}/*** 枚舉常量的序數(shù)(它指的是在枚舉聲明中的位置,其中初始常量的序數(shù)為零)。* 大多數(shù)程序員都不會(huì)使用這個(gè)字段。 它設(shè)計(jì)用于復(fù)雜的基于枚舉型的數(shù)據(jù)結(jié)構(gòu),例如EnumSet,EnumMap。*/private final int ordinal;/*** 返回枚舉常量的序數(shù)*/public final int ordinal() {return ordinal;}/*** 唯一的構(gòu)造函數(shù)。 程序員無(wú)法調(diào)用此構(gòu)造函數(shù)。它由編譯器發(fā)出的代碼用于響應(yīng)枚舉類(lèi)型聲明*/protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}/*** 返回枚舉常量的名稱。雖然沒(méi)有覆蓋的必要性,但該方法允許進(jìn)行覆蓋。* 當(dāng)存在需要更“友好”的字符串形式時(shí),枚舉類(lèi)型類(lèi)應(yīng)該重寫(xiě)此方法。*/public String toString() {return name;}/*** 如果指定的對(duì)象等于此枚舉常量,則返回true。* 【注意】此處比較形式是通過(guò)“==”進(jìn)行,也即是枚舉類(lèi)之間可以通過(guò) == 進(jìn)行比較*/public final boolean equals(Object other) {return this==other;}/*** 返回此枚舉常量的哈希碼*/public final int hashCode() {return super.hashCode();}/*** 拋出CloneNotSupportedException異常. * 這能保證了枚舉常量類(lèi)永遠(yuǎn)不會(huì)被克隆,從而保證其為”單例”狀態(tài)。*/protected final Object clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();}/*** 將此枚舉與指定的對(duì)象進(jìn)行比較以進(jìn)行排序. * 返回一個(gè)負(fù)整數(shù),零或正整數(shù),因?yàn)榇藢?duì)象小于,等于或大于指定的對(duì)象,枚舉常量只能與其他具有相同枚舉類(lèi)型的枚舉常量相相比較.* 此方法實(shí)現(xiàn)的自然順序是聲明常量的順序*/public final int compareTo(E o) {Enum<?> other = (Enum<?>)o;Enum<E> self = this;if (self.getClass() != other.getClass() && // optimizationself.getDeclaringClass() != other.getDeclaringClass())throw new ClassCastException();return self.ordinal - other.ordinal;}/*** 返回與此枚舉常量的枚舉類(lèi)型對(duì)應(yīng)的Class對(duì)象. * 當(dāng)且僅當(dāng)e1.getDeclaringClass()== e2.getDeclaringClass()時(shí), 兩個(gè)枚舉常量e1和e2屬于相同的枚舉類(lèi)型。*/@SuppressWarnings("unchecked")public final Class<E> getDeclaringClass() {Class<?> clazz = getClass();Class<?> zuper = clazz.getSuperclass();return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;}/*** 返回指定枚舉類(lèi)型的枚舉常量指定的名稱. * 名稱必須與聲明此類(lèi)型的枚舉常量使用的標(biāo)識(shí)符完全匹配(不允許使用無(wú)關(guān)的空格字符.)* 請(qǐng)注意,對(duì)于特定的枚舉類(lèi)型T,可以使用該枚舉上隱式聲明的valueOf(String)方法代替此方法從名稱映射到相應(yīng)的枚舉常量。 * 枚舉類(lèi)型的所有常量都可以通過(guò)調(diào)用該類(lèi)型的隱式方法 values()方法來(lái)獲得。** @param <T> 返回常量的枚舉類(lèi)型* @param 枚舉常量類(lèi)型enumType* @param 要返回的枚舉常量的名稱name* @return 返回具有指定名稱和指定枚舉類(lèi)型的枚舉常量* @throws 如果指定的枚舉類(lèi)型沒(méi)有具有指定名稱的常量,或者指定的類(lèi)對(duì)象不表示枚舉類(lèi)型,拋出IllegalArgumentException 異常* @throws 如果enumType或者code name為null,NullPointerException異常*/public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {T result = enumType.enumConstantDirectory().get(name);if (result != null)return result;if (name == null)throw new NullPointerException("Name is null");throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name);}/*** 枚舉類(lèi)不能有finalize方法*/protected final void finalize() { }/*** 無(wú)法反序列化枚舉*/private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {throw new InvalidObjectException("can't deserialize enum");}private void readObjectNoData() throws ObjectStreamException {throw new InvalidObjectException("can't deserialize enum");} }三、疑問(wèn)
1、為什么說(shuō)enum本質(zhì)是一個(gè)繼承了Enum類(lèi)的class?
Java語(yǔ)法就是這么規(guī)定的,還能為啥
2、枚舉聲明為什么是enum不是class,這樣做的意圖是什么?
3、枚舉允許繼承類(lèi)嗎?可以被別人結(jié)成么?
枚舉不允許繼承類(lèi)。Jvm在生成枚舉時(shí)已經(jīng)繼承了Enum類(lèi),由于Java語(yǔ)言是單繼承,不支持再繼承額外的類(lèi)(唯一的繼承名額被Jvm用了)。也不可以繼承枚舉。因?yàn)镴vm在生成枚舉類(lèi)時(shí),將它聲明為final。
4、枚舉可以用等號(hào)比較嗎?
枚舉可以用等號(hào)比較。Jvm會(huì)為每個(gè)枚舉實(shí)例對(duì)應(yīng)生成一個(gè)類(lèi)對(duì)象,這個(gè)類(lèi)對(duì)象是用public static final修飾的,在static代碼塊中初始化,是一個(gè)單例。
5、為什么使用枚舉代替常量類(lèi)?
在我們平常的開(kāi)發(fā)中,為表示同種類(lèi)型的不同種類(lèi),經(jīng)常的做法是聲明一組具名的int常量來(lái)表示,每個(gè)類(lèi)型成員一個(gè)常量,如:
public static final int DAY_MONDAY = 1; public static final int DAY_TUESDAY = 2; public static final int DAY_WEDNESDAY = 3; public static final int DAY_THURSDAY = 4; public static final int DAY_FRIDAY = 5; public static final int DAY_SATURDAY = 6; public static final int DAY_SUNDAY = 7;public static final int ORANGE_NAVEL = 0; public static final int ORANGE_TEMPLE = 1; public static final int ORANGE_BLOOD = 2;這種方法稱做?int枚舉模式,這種方式在安全性和使用方便性方面沒(méi)有任何幫助。
a、將day傳到想要orange的方法中,編譯器不會(huì)警告,執(zhí)行也不會(huì)出現(xiàn)錯(cuò)誤;
b、用==操作符將day與orange比較,編譯器不會(huì)警告,執(zhí)行也不會(huì)出現(xiàn)錯(cuò)誤;
c、int枚舉是編譯時(shí)常量,被編譯到客戶端中,如果枚舉常量關(guān)聯(lián)的int發(fā)生變化,客戶端必須重新編譯,如果沒(méi)有重新編譯,程序仍可以運(yùn)行,但行為就確定了,如DAY_MONDAY關(guān)聯(lián)的常量不再是1,而是0。
d、將int枚舉常量翻譯成可打印的字符串很麻煩
e、如果想要遍歷一個(gè)組中的所有int 枚舉常量,甚至獲得int枚舉組的大小,這種實(shí)現(xiàn)沒(méi)有啥方便可靠的方法。
因此,推薦使用枚舉類(lèi)型來(lái)代替這種int枚舉常量:
public enum DAY {DAY_MONDAY, DAY_TUESDAY, DAY_WEDNESDAY,DAY_THURSDAY, DAY_FRIDAY, DAY_SATURDAY, DAY_SUNDAY}public enum ORANGE {ORANGE_NAVEL, ORANGE_TEMPLE, ORANGE_BLOOD}這種枚舉類(lèi)型,提供了編譯時(shí)的類(lèi)型安全檢查,如果聲明了一個(gè)參數(shù)的類(lèi)型為DAY,就可以保證,被傳到該參數(shù)上的任何非null的對(duì)象引用一定屬于其他有效值中的一個(gè),試圖傳遞類(lèi)型錯(cuò)誤的值時(shí),會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤,就像試圖將某種枚舉類(lèi)型的表達(dá)式賦給另一種枚舉類(lèi)型的變量,或者試圖利用==操作符比較不同枚舉類(lèi)型的值一樣。同時(shí)包含同名常量的多個(gè)枚舉類(lèi)型可以共存,因?yàn)槊總€(gè)類(lèi)型有自己的命名空間,增加或重新排列枚舉類(lèi)型的常量,無(wú)需重新編譯客戶端的代碼。如果想獲取類(lèi)型對(duì)應(yīng)的字符串,直接通過(guò)toString方法即可。
枚舉類(lèi)型除了完善了int枚舉模式的不足之處外,枚舉類(lèi)型還允許添加任意的方法和域,并實(shí)現(xiàn)任意的接口。這個(gè)有什么用途呢?
a、能夠?qū)?shù)據(jù)與它的常量關(guān)聯(lián)起來(lái),例如能夠返回水果顏色或者水果圖片的方法,對(duì)于我們的ORANGE類(lèi)型來(lái)說(shuō)可能就很有好處;
b、你可使用適當(dāng)?shù)姆椒▉?lái)增強(qiáng)枚舉類(lèi)型,枚舉類(lèi)型可以先作為枚舉常量的一個(gè)簡(jiǎn)單集合,隨著時(shí)間的推移在演變成為全功能的抽象。
另外,當(dāng)你想要每增加一種枚舉常量時(shí),需要強(qiáng)制選擇一種對(duì)應(yīng)的策略,可以使用枚舉提供的策略枚舉(strategy enum)?的方式。
4、究竟是枚舉的性能好,還是常量類(lèi)好?
5、為什么枚舉要實(shí)現(xiàn)Comparable接口?
6、為什么枚舉要實(shí)現(xiàn)Serializable接口?
7、為什么枚舉支持泛型?
8、枚舉的底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組還是鏈表?
9、為什么枚舉類(lèi)型實(shí)例化就能訪?比如如下代碼為什么不報(bào)錯(cuò)
public class Traffic{public enum Light{GREEN,YELLOW,RED} } Traffic.Light state = Traffic.Light.GREEN;Java枚舉類(lèi)型都是靜態(tài)常量,隱式的用static final修飾過(guò)。確切的說(shuō),Java枚舉類(lèi)型是“靜態(tài)常量”,這里面包含了兩層意思:
- 枚舉型中的實(shí)例隱式地用static final修飾過(guò)。
- 枚舉型作為某個(gè)類(lèi)中的成員字段也隱式的用static final修飾過(guò)
還是你上面這個(gè)代碼,反編譯一下,你就能看到--編譯器背著你偷偷做了哪些手腳
- 首先,枚舉型Light是個(gè)實(shí)實(shí)在在的類(lèi)。集成自基類(lèi)Enum<Light>。然后在你不知情的情況下,偷偷加了static final修飾詞。然后3個(gè)枚舉實(shí)例?GREEN,YELLOW,RED也確確實(shí)實(shí)是light的實(shí)例,然后前面加了static final。
- 然后構(gòu)造器也被偷偷的閹割成private。這種實(shí)例控制手段,是不是在單例模式里面見(jiàn)過(guò),所以枚舉也是實(shí)現(xiàn)單例器的一種方法。
- 然后編譯器還偷偷的告訴Light[]數(shù)組,一個(gè)values()方法,一個(gè)valueO()f方法,這個(gè)values在Enum文檔里面找不到,如果在Enum里面定義一個(gè)相關(guān)方法,你還會(huì)看到一個(gè)匿名內(nèi)部類(lèi)
反編譯的結(jié)果如下:
總之,Java的Enum枚舉類(lèi)型就是一個(gè)大大的“語(yǔ)法糖”。明明是一個(gè)完整的類(lèi),但只向用戶暴露幾個(gè)常態(tài)變量,隱藏掉大部分實(shí)現(xiàn)細(xì)節(jié)。
上述文字引用自知乎問(wèn)答:Java 枚舉型為什么是靜態(tài)的,以及是怎么實(shí)現(xiàn)的?胖君的回答
10、是不是所有的枚舉都默認(rèn)是靜態(tài)的?
通過(guò)可問(wèn)題5,可知所有的枚舉都默認(rèn)是靜態(tài)的
?
11、枚舉有哪些應(yīng)用場(chǎng)景?
12、枚舉是如何實(shí)現(xiàn)單例的?
public enum Singleton2 {SHERLOCK }class Main{public static void main(String[] args) {Singleton2 sherlock = Singleton2.SHERLOCK;Singleton2 sherlock1 = Singleton2.SHERLOCK;System.out.println(sherlock == Singleton2.SHERLOCK);System.out.println(sherlock == sherlock1);System.out.println(Singleton2.SHERLOCK.getDeclaringClass());} }輸出結(jié)果: true true class com.sherlock.singleton.Singleton2四、Enum常見(jiàn)用法
用法一:常量
在JDK1.5 之前,我們定義常量都是: public static fianl.... 。現(xiàn)在好了,有了枚舉,可以把相關(guān)的常量分組到一個(gè)枚舉類(lèi)型里,而且枚舉提供了比常量更多的方法。
public enum Color { RED, GREEN, BLANK, YELLOW }用法二:switch
JDK1.6之前的switch語(yǔ)句只支持int,char,enum類(lèi)型,使用枚舉,能讓我們的代碼可讀性更強(qiáng)。
enum Signal {GREEN, YELLOW, RED}public class TrafficLight {Signal color = Signal.RED;public void change() {switch (color) {case RED:color = Signal.GREEN;break;case YELLOW:color = Signal.RED;break;case GREEN:color = Signal.YELLOW;break;}} }?
用法三:向枚舉中添加新方法
如果打算自定義自己的方法,那么必須在enum實(shí)例序列的最后添加一個(gè)分號(hào)。而且 Java 要求必須先定義 enum 實(shí)例。
?
public enum Color {RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLO("黃色", 4);// 成員變量private String name;private int index;// 構(gòu)造方法private Color(String name, int index) {this.name = name;this.index = index;}// 普通方法public static String getName(int index) {for (Color c : Color.values()) {if (c.getIndex() == index) {return c.name;}}return null;}// get set 方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getIndex() {return index;}public void setIndex(int index) {this.index = index;} }?
用法四:覆蓋枚舉的方法
下面給出一個(gè)toString()方法覆蓋的例子。
public class Test {public enum Color {RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLO("黃色", 4);// 成員變量private String name;private int index;// 構(gòu)造方法private Color(String name, int index) {this.name = name;this.index = index;}// 覆蓋方法@Overridepublic String toString() {return this.index + "_" + this.name;}}public static void main(String[] args) {System.out.println(Color.RED.toString());} }用法五:實(shí)現(xiàn)接口
所有的枚舉都繼承自java.lang.Enum類(lèi)。由于Java 不支持多繼承,所以枚舉對(duì)象不能再繼承其他類(lèi)。
?
public interface Behaviour {void print();String getInfo(); }public enum Color implements Behaviour {RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLO("黃色", 4);// 成員變量private String name;private int index;// 構(gòu)造方法private Color(String name, int index) {this.name = name;this.index = index;}// 接口方法@Overridepublic String getInfo() {return this.name;}// 接口方法@Overridepublic void print() {System.out.println(this.index + ":" + this.name);} }?
用法六:使用接口組織枚舉?
?
public interface Food {enum Coffee implements Food {BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO}enum Dessert implements Food {FRUIT, CAKE, GELATO} }?
用法七:關(guān)于枚舉集合的使用
java.util.EnumSet和java.util.EnumMap是兩個(gè)枚舉集合。EnumSet保證集合中的元素不重復(fù);EnumMap中的 key是enum類(lèi)型,而value則可以是任意類(lèi)型。關(guān)于這個(gè)兩個(gè)集合的使用就不在這里贅述,可以參考JDK文檔。?完整示例代碼
枚舉類(lèi)型的完整演示代碼如下:
?
public class LightTest {// 1.定義枚舉類(lèi)型public enum Light {// 利用構(gòu)造函數(shù)傳參RED(1), GREEN(3), YELLOW(2);// 定義私有變量private int nCode;// 構(gòu)造函數(shù),枚舉類(lèi)型只能為私有private Light(int _nCode) {this.nCode = _nCode;}@Overridepublic String toString() {return String.valueOf(this.nCode);}}public static void main(String[] args) {// 1.遍歷枚舉類(lèi)型System.out.println("演示枚舉類(lèi)型的遍歷 ......");testTraversalEnum();// 2.演示EnumMap對(duì)象的使用System.out.println("演示EnmuMap對(duì)象的使用和遍歷.....");testEnumMap();// 3.演示EnmuSet的使用System.out.println("演示EnmuSet對(duì)象的使用和遍歷.....");testEnumSet();}/*** * 演示枚舉類(lèi)型的遍歷*/private static void testTraversalEnum() {Light[] allLight = Light.values();for (Light aLight : allLight) {System.out.println("當(dāng)前燈name:" + aLight.name());System.out.println("當(dāng)前燈ordinal:" + aLight.ordinal());System.out.println("當(dāng)前燈:" + aLight);}}/*** * 演示EnumMap的使用,EnumMap跟HashMap的使用差不多,只不過(guò)key要是枚舉類(lèi)型*/private static void testEnumMap() {// 1.演示定義EnumMap對(duì)象,EnumMap對(duì)象的構(gòu)造函數(shù)需要參數(shù)傳入,默認(rèn)是key的類(lèi)的類(lèi)型EnumMap<Light, String> currEnumMap = new EnumMap<Light, String>(Light.class);currEnumMap.put(Light.RED, "紅燈");currEnumMap.put(Light.GREEN, "綠燈");currEnumMap.put(Light.YELLOW, "黃燈");// 2.遍歷對(duì)象for (Light aLight : Light.values()) {System.out.println("[key=" + aLight.name() + ",value="+ currEnumMap.get(aLight) + "]");}}/*** * 演示EnumSet如何使用,EnumSet是一個(gè)抽象類(lèi),獲取一個(gè)類(lèi)型的枚舉類(lèi)型內(nèi)容<BR/>* * 可以使用allOf方法*/private static void testEnumSet() {EnumSet<Light> currEnumSet = EnumSet.allOf(Light.class);for (Light aLightSetElement : currEnumSet) {System.out.println("當(dāng)前EnumSet中數(shù)據(jù)為:" + aLightSetElement);}} }?
執(zhí)行結(jié)果如下:
演示枚舉類(lèi)型的遍歷 ...... 當(dāng)前燈name:RED 當(dāng)前燈ordinal:0 當(dāng)前燈:1 當(dāng)前燈name:GREEN 當(dāng)前燈ordinal:1 當(dāng)前燈:3 當(dāng)前燈name:YELLOW 當(dāng)前燈ordinal:2 當(dāng)前燈:2 演示EnmuMap對(duì)象的使用和遍歷..... [key=RED,value=紅燈] [key=GREEN,value=綠燈] [key=YELLOW,value=黃燈] 演示EnmuSet對(duì)象的使用和遍歷..... 當(dāng)前EnumSet中數(shù)據(jù)為:1 當(dāng)前EnumSet中數(shù)據(jù)為:3 當(dāng)前EnumSet中數(shù)據(jù)為:2讀后有收獲可以支付寶請(qǐng)作者喝奶茶?
?
總結(jié)
以上是生活随笔為你收集整理的java.lang包—枚举类Enum的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java.lang包—基本类型的封装类
- 下一篇: JVM—如何利用虚拟机栈进行函数调用?