理解Java枚举类型
?(參考資料:深入理解java enum)
1、原理:對(duì)編譯后的class文件javap反編譯可以看出,定義的枚舉類繼承自java.lang.Enum抽象類且通過public static final定義了幾個(gè)常量作為枚舉常量。示例:
1 //定義枚舉類型 2 enum Day { 3 MONDAY, TUESDAY, WEDNESDAY, 4 THURSDAY, FRIDAY, SATURDAY, SUNDAY 5 } 6 7 //對(duì)應(yīng)的完整內(nèi)容 8 //反編譯Day.class 9 final class Day extends Enum 10 { 11 //編譯器為我們添加的靜態(tài)的values()方法 12 public static Day[] values() 13 { 14 return (Day[])$VALUES.clone(); 15 } 16 //編譯器為我們添加的靜態(tài)的valueOf()方法,注意間接調(diào)用了Enum也類的valueOf方法 17 public static Day valueOf(String s) 18 { 19 return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s); 20 } 21 //私有構(gòu)造函數(shù) 22 private Day(String s, int i) 23 { 24 super(s, i); 25 } 26 //前面定義的7種枚舉實(shí)例 27 public static final Day MONDAY; 28 public static final Day TUESDAY; 29 public static final Day WEDNESDAY; 30 public static final Day THURSDAY; 31 public static final Day FRIDAY; 32 public static final Day SATURDAY; 33 public static final Day SUNDAY; 34 private static final Day $VALUES[]; 35 36 static 37 { 38 //實(shí)例化枚舉實(shí)例 39 MONDAY = new Day("MONDAY", 0); 40 TUESDAY = new Day("TUESDAY", 1); 41 WEDNESDAY = new Day("WEDNESDAY", 2); 42 THURSDAY = new Day("THURSDAY", 3); 43 FRIDAY = new Day("FRIDAY", 4); 44 SATURDAY = new Day("SATURDAY", 5); 45 SUNDAY = new Day("SUNDAY", 6); 46 $VALUES = (new Day[] { 47 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY 48 }); 49 } 50 } 枚舉類反編譯后的源碼java.lang.Enum抽象類定義了一些方法:
| int | compareTo(E o) | 比較此枚舉與指定對(duì)象的順序 |
| boolean | equals(Object other) | 當(dāng)指定對(duì)象等于此枚舉常量時(shí),返回 true。 |
| Class<?> | getDeclaringClass() | 返回與此枚舉常量的枚舉類型相對(duì)應(yīng)的 Class 對(duì)象 |
| String | name() | 返回此枚舉常量的名稱,在其枚舉聲明中對(duì)其進(jìn)行聲明 |
| int | ordinal() | 返回枚舉常量的序數(shù)(它在枚舉聲明中的位置,其中初始常量序數(shù)為零) |
| String | toString() | 返回枚舉常量的名稱,它包含在聲明中 |
| static<T extends Enum<T>> T | static valueOf(Class<T> enumType, String name) | 返回帶指定名稱的指定枚舉類型的枚舉常量。 |
?主要源碼:
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {private final String name; //枚舉字符串名稱public final String name() {return name;}private final int ordinal;//枚舉順序值public final int ordinal() {return ordinal;}//枚舉的構(gòu)造方法,只能由編譯器調(diào)用protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}public String toString() {return name;}public final boolean equals(Object other) {return this==other;}//比較的是ordinal值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;//根據(jù)ordinal值比較大小 }@SuppressWarnings("unchecked")public final Class<E> getDeclaringClass() {//獲取class對(duì)象引用,getClass()是Object的方法Class<?> clazz = getClass();//獲取父類Class對(duì)象引用Class<?> zuper = clazz.getSuperclass();return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;}public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {//enumType.enumConstantDirectory()獲取到的是一個(gè)map集合,key值就是name值,value則是枚舉變量值 //enumConstantDirectory是class對(duì)象內(nèi)部的方法,根據(jù)class對(duì)象獲取一個(gè)map集合的值 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);}//.....省略其他沒用的方法 } java.lang.Enum2、可以把枚舉類型當(dāng)成常規(guī)類,即我們可以向枚舉類中添加方法和變量。但是枚舉常量定義必須在方法定義前面,否則編譯報(bào)錯(cuò)。示例:
1 public enum Day2 { 2 MONDAY("星期一"), 3 TUESDAY("星期二"), 4 WEDNESDAY("星期三"), 5 THURSDAY("星期四"), 6 FRIDAY("星期五"), 7 SATURDAY("星期六"), 8 SUNDAY("星期日");//記住要用分號(hào)結(jié)束 9 10 private String desc;//中文描述 11 12 /** 13 * 私有構(gòu)造,防止被外部調(diào)用 14 * @param desc 15 */ 16 private Day2(String desc){ 17 this.desc=desc; 18 } 19 20 /** 21 * 定義方法,返回描述,跟常規(guī)類的定義沒區(qū)別 22 * @return 23 */ 24 public String getDesc(){ 25 return desc; 26 } 27 28 public static void main(String[] args){ 29 for (Day2 day:Day2.values()) { 30 System.out.println("name:"+day.name()+ 31 ",desc:"+day.getDesc()); 32 } 33 } 34 35 /** 36 輸出結(jié)果: 37 name:MONDAY,desc:星期一 38 name:TUESDAY,desc:星期二 39 name:WEDNESDAY,desc:星期三 40 name:THURSDAY,desc:星期四 41 name:FRIDAY,desc:星期五 42 name:SATURDAY,desc:星期六 43 name:SUNDAY,desc:星期日 44 */ 45 } 枚舉類型自定義方法 public enum EnumDemo3 {FIRST{@Overridepublic String getInfo() {return "FIRST TIME";}},SECOND{@Overridepublic String getInfo() {return "SECOND TIME";}};/*** 定義抽象方法* @return*/public abstract String getInfo();//測(cè)試public static void main(String[] args){System.out.println("F:"+EnumDemo3.FIRST.getInfo());System.out.println("S:"+EnumDemo3.SECOND.getInfo());/**輸出結(jié)果:F:FIRST TIMES:SECOND TIME*/} } 枚舉類型中定義抽象方法3、定義的枚舉類型無法被繼承(看反編譯后的源碼可知類被final修飾了)也無法繼承其他類(因其已默認(rèn)繼承了Enum類,而Java只允許單繼承),但可以實(shí)現(xiàn)接口。一個(gè)很好的示例:
public enum Meal{APPETIZER(Food.Appetizer.class),MAINCOURSE(Food.MainCourse.class),DESSERT(Food.Dessert.class),COFFEE(Food.Coffee.class);private Food[] values;private Meal(Class<? extends Food> kind) {//通過class對(duì)象獲取枚舉實(shí)例values = kind.getEnumConstants();}public interface Food {enum Appetizer implements Food {SALAD, SOUP, SPRING_ROLLS;}enum MainCourse implements Food {LASAGNE, BURRITO, PAD_THAI,LENTILS, HUMMOUS, VINDALOO;}enum Dessert implements Food {TIRAMISU, GELATO, BLACK_FOREST_CAKE,FRUIT, CREME_CARAMEL;}enum Coffee implements Food {BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,LATTE, CAPPUCCINO, TEA, HERB_TEA;}} } 枚舉類實(shí)現(xiàn)接口?4、枚舉與單例:使用枚舉單例的寫法,我們完全不用考慮序列化和反射的問題。枚舉序列化是由jvm保證的,每一個(gè)枚舉類型和定義的枚舉變量在JVM中都是唯一的,在枚舉類型的序列化和反序列化上,Java做了特殊的規(guī)定:在序列化時(shí)Java僅僅是將枚舉對(duì)象的name屬性輸出到結(jié)果中,反序列化的時(shí)候則是通過java.lang.Enum的valueOf方法來根據(jù)名字查找枚舉對(duì)象。同時(shí),編譯器是不允許任何對(duì)這種序列化機(jī)制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,從而保證了枚舉實(shí)例的唯一性(也說明了只有Java中只有編譯器能創(chuàng)建枚舉實(shí)例)。
如何確保反序列化時(shí)不會(huì)破壞單例:根據(jù)valueOf(name)得到反序列化后對(duì)象,valueOf根據(jù)枚舉常量名獲取對(duì)應(yīng)枚舉常量
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);}Map<String, T> enumConstantDirectory() {if (enumConstantDirectory == null) {//getEnumConstantsShared最終通過反射調(diào)用枚舉類的values方法T[] universe = getEnumConstantsShared();if (universe == null)throw new IllegalArgumentException(getName() + " is not an enum type");Map<String, T> m = new HashMap<>(2 * universe.length);//map存放了當(dāng)前enum類的所有枚舉實(shí)例變量,以name為key值for (T constant : universe)m.put(((Enum<?>)constant).name(), constant);enumConstantDirectory = m;}return enumConstantDirectory;}private volatile transient Map<String, T> enumConstantDirectory = null; valueOf如何確保反射不會(huì)破壞單例:反射源碼里對(duì)于枚舉類型反射直接拋異常所以反射生成不了枚舉類型實(shí)例
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {//獲取枚舉類的構(gòu)造函數(shù)(前面的源碼已分析過)Constructor<SingletonEnum> constructor=SingletonEnum.class.getDeclaredConstructor(String.class,int.class);constructor.setAccessible(true);//創(chuàng)建枚舉SingletonEnum singleton=constructor.newInstance("otherInstance",9);}//運(yùn)行結(jié)果 Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objectsat java.lang.reflect.Constructor.newInstance(Constructor.java:417)at zejian.SingletonEnum.main(SingletonEnum.java:38)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)//newInstance源碼public T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}//這里判斷Modifier.ENUM是不是枚舉修飾符,如果是就拋異常if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor; // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;} View Code在單例中,枚舉也不是萬能的。在android開發(fā)中,內(nèi)存優(yōu)化是個(gè)大塊頭,而使用枚舉時(shí)占用的內(nèi)存常常是靜態(tài)變量的兩倍還多,因此android官方在內(nèi)存優(yōu)化方面給出的建議是盡量避免在android中使用enum。
5、EnumMap與EnumSet:見上述參考資料。
前者與HashMap類似,只不過key是Enum類型且不能為null。
后者則采用位向量實(shí)現(xiàn),對(duì)于枚舉值個(gè)數(shù)少于64的用一個(gè)long來標(biāo)記(RegularEnumSet)否則用long[ ]來標(biāo)記(JumboEnumSet)。
?
轉(zhuǎn)載于:https://www.cnblogs.com/z-sm/p/9299792.html
總結(jié)
以上是生活随笔為你收集整理的理解Java枚举类型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTML5实现屏幕手势解锁(转载)
- 下一篇: HDU6376 度度熊剪纸条