Java枚举类型(enum)
枚举类型实现原理
使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承Java API中的java.lang.Enum类,就是说通过关键字enum创建枚举类型,在编译后也是一个类类型而且该类继承自java.lang.Enum类
//定义枚举类型 enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
反编译Day.class文件
//反编译Day.class final class Day extends Enum { //编译器为我们添加的静态的values()方法 public static Day[] values() { return (Day[])$VALUES.clone(); } //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法 public static Day valueOf(String s) { return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s); } //私有构造函数 private Day(String s, int i) { super(s, i); } //前面定义的7种枚举实例 public static final Day MONDAY; public static final Day TUESDAY; public static final Day WEDNESDAY; public static final Day THURSDAY; public static final Day FRIDAY; public static final Day SATURDAY; public static final Day SUNDAY; private static final Day $VALUES[]; static { //实例化枚举实例 MONDAY = new Day("MONDAY", 0); TUESDAY = new Day("TUESDAY", 1); WEDNESDAY = new Day("WEDNESDAY", 2); THURSDAY = new Day("THURSDAY", 3); FRIDAY = new Day("FRIDAY", 4); SATURDAY = new Day("SATURDAY", 5); SUNDAY = new Day("SUNDAY", 6); $VALUES = (new Day[] { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }); } }
生成了一个Day类(注意该类是final类型的,将无法被继承)而且该类继承自java.lang.Enum类,该类是一个抽象类,除此之外,编译器还帮助我们生成了7个Day类型的实例对象分别对应枚举中定义的7个日期,这也充分说明使用关键字enum定义的Day类型中的每种日期枚举常量也是实实在在的Day实例对象。注意编译器还为生成两个静态方法,分别是values()和 valueOf(),使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;

public class EnumDemo { public static void main(String[] args){ //创建枚举数组 Day[] days=new Day[]{Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY, Day.THURSDAY, Day.FRIDAY, Day.SATURDAY, Day.SUNDAY}; for (int i = 0; i <days.length ; i++) { System.out.println("day["+i+"].ordinal():"+days[i].ordinal()); } System.out.println("-------------------------------------"); //通过compareTo方法比较,实际上其内部是通过ordinal()值比较的 System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[1])); System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[2])); //获取该枚举对象的Class对象引用,当然也可以通过getClass方法 Class<?> clazz = days[0].getDeclaringClass(); System.out.println("clazz:"+clazz); System.out.println("-------------------------------------"); //name() System.out.println("days[0].name():"+days[0].name()); System.out.println("days[1].name():"+days[1].name()); System.out.println("days[2].name():"+days[2].name()); System.out.println("days[3].name():"+days[3].name()); System.out.println("-------------------------------------"); System.out.println("days[0].toString():"+days[0].toString()); System.out.println("days[1].toString():"+days[1].toString()); System.out.println("days[2].toString():"+days[2].toString()); System.out.println("days[3].toString():"+days[3].toString()); System.out.println("-------------------------------------"); Day d=Enum.valueOf(Day.class,days[0].name()); Day d2=Day.valueOf(Day.class,days[0].name()); System.out.println("d:"+d); System.out.println("d2:"+d2); } /** 执行结果: day[0].ordinal():0 day[1].ordinal():1 day[2].ordinal():2 day[3].ordinal():3 day[4].ordinal():4 day[5].ordinal():5 day[6].ordinal():6 ------------------------------------- days[0].compareTo(days[1]):-1 days[0].compareTo(days[1]):-2 clazz:class com.zejian.enumdemo.Day ------------------------------------- days[0].name():MONDAY days[1].name():TUESDAY days[2].name():WEDNESDAY days[3].name():THURSDAY ------------------------------------- days[0].toString():MONDAY days[1].toString():TUESDAY days[2].toString():WEDNESDAY days[3].toString():THURSDAY ------------------------------------- d:MONDAY d2:MONDAY */ } enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
Day[] days2 = Day.values(); System.out.println("day2:"+Arrays.toString(days2)); Day day = Day.valueOf("MONDAY"); System.out.println("day:"+day); /** 输出结果: day2:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY] day:MONDAY */
values()方法的作用就是获取枚举类中的所有变量,并作为数组返回,而valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量,只不过编译器生成的valueOf方法更简洁些只需传递一个参数。这里我们还必须注意到,由于values()方法是由编译器插入到枚举类中的static方法,所以如果我们将枚举实例向上转型为Enum,那么values()方法将无法被调用,因为Enum类中并没有values()方法,valueOf()方法也是同样的道理,注意是一个参数的。
//正常使用 Day[] ds=Day.values(); //向上转型Enum Enum e = Day.MONDAY; //无法调用,没有此方法 //e.values();
提到当枚举实例向上转型为Enum类型后,values()方法将会失效,也就无法一次性获取所有枚举实例变量,但是由于Class对象的存在,即使不使用values()方法,还是有可能一次获取到所有枚举实例变量的,在Class对象中存在如下方法:

//正常使用 Day[] ds=Day.values(); //向上转型Enum Enum e = Day.MONDAY; //无法调用,没有此方法 //e.values(); //获取class对象引用 Class<?> clasz = e.getDeclaringClass(); if(clasz.isEnum()) { Day[] dsz = (Day[]) clasz.getEnumConstants(); System.out.println("dsz:"+Arrays.toString(dsz)); } /** 输出结果: dsz:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY] */
enum类中定义抽象方法
enum类允许我们为其定义抽象方法,然后使每个枚举实例都实现该方法,以便产生不同的行为方式,注意abstract关键字对于枚举类来说并不是必须的如下:
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 */ } }
需要对一组数据进行分类,比如进行食物菜单分类而且希望这些菜单都属于food类型,appetizer(开胃菜)、mainCourse(主菜)、dessert(点心)、Coffee等,每种分类下有多种具体的菜式或食品,此时可以利用接口来组织
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; } } public class TypeOfFood { public static void main(String[] args) { Food food = Appetizer.SALAD; food = MainCourse.LASAGNE; food = Dessert.GELATO; food = Coffee.CAPPUCCINO; } }
枚举型确实也被switch所支持,在java 1.7后switch也对字符串进行了支持
enum Color {GREEN,RED,BLUE} public class EnumDemo4 { public static void printName(Color color){ switch (color){ case BLUE: //无需使用Color进行引用 System.out.println("蓝色"); break; case RED: System.out.println("红色"); break; case GREEN: System.out.println("绿色"); break; } } public static void main(String[] args){ printName(Color.BLUE); printName(Color.RED); printName(Color.GREEN); //蓝色 //红色 //绿色 } }
枚举单例
public enum SingletonEnum { INSTANCE; private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } }
使用SingletonEnum.INSTANCE进行访问,也就避免调用getInstance方法,更重要的是使用枚举单例的写法,完全不用考虑序列化和反射的问题。枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制,并禁用writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性,不妨再次看看Enum类的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); }
通过调用enumType(Class对象的引用)的enumConstantDirectory方法获取到的是一个Map集合,在该集合中存放了以枚举name为key和以枚举实例变量为value的Key&Value数据,因此通过name的值就可以获取到枚举实例,看看enumConstantDirectory方法源码:
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;
枚举序列化确实不会重新创建新实例,jvm保证了每个枚举实例变量的唯一性,看看反射到底能不能创建枚举,下面试图通过反射获取构造器并创建枚举:
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; }
https://blog.csdn.net/javazejian/article/details/71333103(推荐)
浙公网安备 33010602011771号