理解Java枚举类型
?(參考資料:深入理解java enum)
1、原理:對編譯后的class文件javap反編譯可以看出,定義的枚舉類繼承自java.lang.Enum抽象類且通過public static final定義了幾個常量作為枚舉常量。示例:
1 //定義枚舉類型 2 enum Day { 3 MONDAY, TUESDAY, WEDNESDAY, 4 THURSDAY, FRIDAY, SATURDAY, SUNDAY 5 } 6 7 //對應的完整內容 8 //反編譯Day.class 9 final class Day extends Enum 10 { 11 //編譯器為我們添加的靜態的values()方法 12 public static Day[] values() 13 { 14 return (Day[])$VALUES.clone(); 15 } 16 //編譯器為我們添加的靜態的valueOf()方法,注意間接調用了Enum也類的valueOf方法 17 public static Day valueOf(String s) 18 { 19 return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s); 20 } 21 //私有構造函數 22 private Day(String s, int i) 23 { 24 super(s, i); 25 } 26 //前面定義的7種枚舉實例 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 //實例化枚舉實例 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) | 比較此枚舉與指定對象的順序 |
| boolean | equals(Object other) | 當指定對象等于此枚舉常量時,返回 true。 |
| Class<?> | getDeclaringClass() | 返回與此枚舉常量的枚舉類型相對應的 Class 對象 |
| String | name() | 返回此枚舉常量的名稱,在其枚舉聲明中對其進行聲明 |
| int | ordinal() | 返回枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量序數為零) |
| 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;}//枚舉的構造方法,只能由編譯器調用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;//根據ordinal值比較大小 }@SuppressWarnings("unchecked")public final Class<E> getDeclaringClass() {//獲取class對象引用,getClass()是Object的方法Class<?> clazz = getClass();//獲取父類Class對象引用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()獲取到的是一個map集合,key值就是name值,value則是枚舉變量值 //enumConstantDirectory是class對象內部的方法,根據class對象獲取一個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、可以把枚舉類型當成常規類,即我們可以向枚舉類中添加方法和變量。但是枚舉常量定義必須在方法定義前面,否則編譯報錯。示例:
1 public enum Day2 { 2 MONDAY("星期一"), 3 TUESDAY("星期二"), 4 WEDNESDAY("星期三"), 5 THURSDAY("星期四"), 6 FRIDAY("星期五"), 7 SATURDAY("星期六"), 8 SUNDAY("星期日");//記住要用分號結束 9 10 private String desc;//中文描述 11 12 /** 13 * 私有構造,防止被外部調用 14 * @param desc 15 */ 16 private Day2(String desc){ 17 this.desc=desc; 18 } 19 20 /** 21 * 定義方法,返回描述,跟常規類的定義沒區別 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 輸出結果: 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();//測試public static void main(String[] args){System.out.println("F:"+EnumDemo3.FIRST.getInfo());System.out.println("S:"+EnumDemo3.SECOND.getInfo());/**輸出結果:F:FIRST TIMES:SECOND TIME*/} } 枚舉類型中定義抽象方法3、定義的枚舉類型無法被繼承(看反編譯后的源碼可知類被final修飾了)也無法繼承其他類(因其已默認繼承了Enum類,而Java只允許單繼承),但可以實現接口。一個很好的示例:
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對象獲取枚舉實例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;}} } 枚舉類實現接口?4、枚舉與單例:使用枚舉單例的寫法,我們完全不用考慮序列化和反射的問題。枚舉序列化是由jvm保證的,每一個枚舉類型和定義的枚舉變量在JVM中都是唯一的,在枚舉類型的序列化和反序列化上,Java做了特殊的規定:在序列化時Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化機制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,從而保證了枚舉實例的唯一性(也說明了只有Java中只有編譯器能創建枚舉實例)。
如何確保反序列化時不會破壞單例:根據valueOf(name)得到反序列化后對象,valueOf根據枚舉常量名獲取對應枚舉常量
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最終通過反射調用枚舉類的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存放了當前enum類的所有枚舉實例變量,以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如何確保反射不會破壞單例:反射源碼里對于枚舉類型反射直接拋異常所以反射生成不了枚舉類型實例
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {//獲取枚舉類的構造函數(前面的源碼已分析過)Constructor<SingletonEnum> constructor=SingletonEnum.class.getDeclaredConstructor(String.class,int.class);constructor.setAccessible(true);//創建枚舉SingletonEnum singleton=constructor.newInstance("otherInstance",9);}//運行結果 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開發中,內存優化是個大塊頭,而使用枚舉時占用的內存常常是靜態變量的兩倍還多,因此android官方在內存優化方面給出的建議是盡量避免在android中使用enum。
5、EnumMap與EnumSet:見上述參考資料。
前者與HashMap類似,只不過key是Enum類型且不能為null。
后者則采用位向量實現,對于枚舉值個數少于64的用一個long來標記(RegularEnumSet)否則用long[ ]來標記(JumboEnumSet)。
?
轉載于:https://www.cnblogs.com/z-sm/p/9299792.html
總結
以上是生活随笔為你收集整理的理解Java枚举类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTML5实现屏幕手势解锁(转载)
- 下一篇: HDU6376 度度熊剪纸条