理解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() && // optimization self.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); } //.....省略其他没用的方法 }
2、可以把枚举类型当成常规类,即我们可以向枚举类中添加方法和变量。但是枚举常量定义必须在方法定义前面,否则编译报错。示例:
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{ @Override public String getInfo() { return "FIRST TIME"; } }, SECOND{ @Override public 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 TIME S: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;
如何确保反射不会破坏单例:反射源码里对于枚举类型反射直接抛异常所以反射生成不了枚举类型实例
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 objects at 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 volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
在单例中,枚举也不是万能的。在android开发中,内存优化是个大块头,而使用枚举时占用的内存常常是静态变量的两倍还多,因此android官方在内存优化方面给出的建议是尽量避免在android中使用enum。
5、EnumMap与EnumSet:见上述参考资料。
前者与HashMap类似,只不过key是Enum类型且不能为null。
后者则采用位向量实现,对于枚举值个数少于64的用一个long来标记(RegularEnumSet)否则用long[ ]来标记(JumboEnumSet)。