1、原理:对编译后的class文件javap反编译可以看出,定义的枚举类继承自java.lang.Enum抽象类且通过public static final定义了几个常量作为枚举常量。示例:
1 //定义枚举类型
2 enumDay {3 MONDAY, TUESDAY, WEDNESDAY,4 THURSDAY, FRIDAY, SATURDAY, SUNDAY5 }6
7 //对应的完整内容8 //反编译Day.class
9 final class Day extendsEnum10 {11 //编译器为我们添加的静态的values()方法
12 public staticDay[] values()13 {14 return(Day[])$VALUES.clone();15 }16 //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
17 public staticDay valueOf(String s)18 {19 return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);20 }21 //私有构造函数
22 private Day(String s, inti)23 {24 super(s, i);25 }26 //前面定义的7种枚举实例
27 public static finalDay MONDAY;28 public static finalDay TUESDAY;29 public static finalDay WEDNESDAY;30 public static finalDay THURSDAY;31 public static finalDay FRIDAY;32 public static finalDay SATURDAY;33 public static finalDay SUNDAY;34 private static finalDay $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 = (newDay[] {47 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY48 });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
static valueOf(Class enumType, String name)
返回带指定名称的指定枚举类型的枚举常量。
主要源码:
public abstract class Enum>
implements Comparable, Serializable {private final String name; //枚举字符串名称
public finalString name() {returnname;
}private final int ordinal;//枚举顺序值
public final intordinal() {returnordinal;
}//枚举的构造方法,只能由编译器调用
protected Enum(String name, intordinal) {this.name =name;this.ordinal =ordinal;
}publicString toString() {returnname;
}public final booleanequals(Object other) {return this==other;
}//比较的是ordinal值
public final intcompareTo(E o) {
Enum> other = (Enum>)o;
Enum self = this;if (self.getClass() != other.getClass() && //optimization
self.getDeclaringClass() !=other.getDeclaringClass())throw newClassCastException();return self.ordinal - other.ordinal;//根据ordinal值比较大小
}
@SuppressWarnings("unchecked")public final ClassgetDeclaringClass() {//获取class对象引用,getClass()是Object的方法
Class> clazz =getClass();//获取父类Class对象引用
Class> zuper =clazz.getSuperclass();return (zuper == Enum.class) ? (Class)clazz : (Class)zuper;
}public static > T valueOf(ClassenumType,
String name) {//enumType.enumConstantDirectory()获取到的是一个map集合,key值就是name值,value则是枚举变量值//enumConstantDirectory是class对象内部的方法,根据class对象获取一个map集合的值
T result =enumType.enumConstantDirectory().get(name);if (result != null)returnresult;if (name == null)throw new NullPointerException("Name is null");throw newIllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." +name);
}//.....省略其他没用的方法
}
java.lang.Enum
2、可以把枚举类型当成常规类,即我们可以向枚举类中添加方法和变量。但是枚举常量定义必须在方法定义前面,否则编译报错。示例:
1 public enumDay2 {2 MONDAY("星期一"),3 TUESDAY("星期二"),4 WEDNESDAY("星期三"),5 THURSDAY("星期四"),6 FRIDAY("星期五"),7 SATURDAY("星期六"),8 SUNDAY("星期日");//记住要用分号结束
9
10 private String desc;//中文描述
11
12 /**
13 * 私有构造,防止被外部调用14 *@paramdesc15 */
16 privateDay2(String desc){17 this.desc=desc;18 }19
20 /**
21 * 定义方法,返回描述,跟常规类的定义没区别22 *@return
23 */
24 publicString getDesc(){25 returndesc;26 }27
28 public static voidmain(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 enumEnumDemo3 {
FIRST{
@OverridepublicString getInfo() {return "FIRST TIME";
}
},
SECOND{
@OverridepublicString getInfo() {return "SECOND TIME";
}
}
;/*** 定义抽象方法
*@return
*/
public abstractString getInfo();//测试
public static voidmain(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 enumMeal{
APPETIZER(Food.Appetizer.class),
MAINCOURSE(Food.MainCourse.class),
DESSERT(Food.Dessert.class),
COFFEE(Food.Coffee.class);privateFood[] values;private Meal(Class extends Food>kind) {//通过class对象获取枚举实例
values =kind.getEnumConstants();
}public interfaceFood {enum Appetizer implementsFood {
SALAD, SOUP, SPRING_ROLLS;
}enum MainCourse implementsFood {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}enum Dessert implementsFood {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}enum Coffee implementsFood {
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 valueOf(ClassenumType,
String name) {
T result=enumType.enumConstantDirectory().get(name);if (result != null)returnresult;if (name == null)throw new NullPointerException("Name is null");throw newIllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." +name);
}
MapenumConstantDirectory() {if (enumConstantDirectory == null) {//getEnumConstantsShared最终通过反射调用枚举类的values方法
T[] universe =getEnumConstantsShared();if (universe == null)throw newIllegalArgumentException(
getName()+ " is not an enum type");
Map m = new HashMap<>(2 *universe.length);//map存放了当前enum类的所有枚举实例变量,以name为key值
for(T constant : universe)
m.put(((Enum>)constant).name(), constant);
enumConstantDirectory=m;
}returnenumConstantDirectory;
}private volatile transient Map enumConstantDirectory = null;
valueOf
如何确保反射不会破坏单例:反射源码里对于枚举类型反射直接抛异常所以反射生成不了枚举类型实例
public static void main(String[] args) throwsIllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {//获取枚举类的构造函数(前面的源码已分析过)
Constructor 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 enumobjects
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源码
publicT newInstance(Object ... initargs)throwsInstantiationException, 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);returninst;
}
View Code
在单例中,枚举也不是万能的。在android开发中,内存优化是个大块头,而使用枚举时占用的内存常常是静态变量的两倍还多,因此android官方在内存优化方面给出的建议是尽量避免在android中使用enum。
5、EnumMap与EnumSet:见上述参考资料。
前者与HashMap类似,只不过key是Enum类型且不能为null。
后者则采用位向量实现,对于枚举值个数少于64的用一个long来标记(RegularEnumSet)否则用long[ ]来标记(JumboEnumSet)。