首先,直接继承抽象类Enum而不选择使用enum关键字创建枚举类是行不通的,通不过编译:
枚举类是实现单例的一个不错选择。
枚举类构造函数默认为private。
Enum 一般用来表示一组相同类型的常量。如性别、日期、月份、颜色等。
枚举类对象在枚举类中以public static final 修饰,但自定义枚举类时,可以添加任意访问修饰符的类变量和实例变量。
使用enum声明的枚举类默认为final class 因此不能被继承。
所有enum都继承了java.lang.Enum抽象类,该类的声明为
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable
该抽象类中实现的 oridinal、equals、hashCode、clone、compareTo等方法均声明为final,因此自定义的enum不能重写这些方法。
比如对于clone方法,由于每个枚举对象都是singleton,禁止对枚举对象克隆,因此将clone方法声明为final合情合理。
下面来看抽象类Enum的compareTo方法
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;
}
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
getClass()为继承自Object,返回当前对象运行时实际类型对应的Class对象,而getDeclaringClass()为Enum类中的方法,首先获取当前对象的实际class,如果为Enum.class,那么将其返回,如果不是,则返回其超类。
而compareTo方法比较两个对象时,如果两个对象的实际类型和父类型
都不相等,就会抛出类型转换异常。这里比较两个对象的实际类型比较好理解,那为什么要比较对象实际类型的超类型呢?
stackoverflow中给出了答案:
http://stackoverflow.com/questions/5758660/java-enum-getdeclaringclass-vs-getclass
Java enum values are permitted to have value-specific class bodies, e.g. (and I hope this syntax is correct...)
public enum MyEnum {
A {
void doSomething() { ... }
},
B {
void doSomethingElse() { ... }
};
}
This will generate inner classes representing the class bodies for A and B. These inner classes will be subclasses of MyEnum.
MyEnum.A.getClass() will return the anonymous class representing A's class body, which may not be what you want.
MyEnum.A.getDeclaringClass(), on the other hand, will return the Class object representing MyEnum.
For simple enums (i.e. ones without constant-specific class bodies), getClass() and getDeclaringClass() return the same thing.
在我们编写的enum类中,可以声明带body的枚举对象,也就是会生成所谓的匿名类,那么此时该枚举对象的运行时实际类型即为此匿名内部类,而该匿名内部类继承了MyEnum。因此在用compareTo方法比较枚举对象时,不能只靠getClass判断这两个对象类型是否相同。
在java代码中,我们使用 enum
关键字声明一个枚举类,编译器碰到此关键字时,会进行特殊处理,将该名字的枚举类自动extends java.lang.Enum(这也就是枚举类不能显式继承类的原因,单继承),因此该枚举类便自动继承了来自抽象类Enum的那些实例方法。
使用 enum
关键字声明一个枚举类还继承了来自抽象类Enum的静态方法(静态方法可以继承而不能重写,只能隐藏)
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);
}
enumConstantDirectory()为Object类方法
Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map<String, T> m = new HashMap<>(2 * universe.length);
for (T constant : universe)
m.put(((Enum<?>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return enumConstants;
}
从上面代码可以看出,valueOf(Class< T > enumType, String name) 方法获取枚举类对象为懒加载,首先判断enumConstantDirectory 这个map是否为空,如果为空则进行第一次初始化加载枚举类对象,构造枚举类对象的工作是通过反射调用真正的枚举类的values()方法(马上谈到)。
另外,编译器还为新生成的枚举类生成了两个静态方法,重载的valueOf(String name) 和一个values()方法,通过反编译class文件,得到这两个方法的字节码:
values()方法目前没有看懂,不明白是怎么实现对枚举类构造函数实现懒调用的。但看字节码,每次都拷贝一个新的枚举类对象数组引用。
valueOf()方法内部调用了抽象类Enum的valueOf()方法,参数为String,根据定义枚举类对象时的字符串名称进行查找。
枚举类在实际开发中的应用
由于在数据库表的设计中,经常会有表示状态的字段,值为1、2、3..用来表示不同的状态,比如1代表待付款,2代表待发货,3代表已发货。使用枚举类来作为数据库状态字段值的映射,在数据存入、查询时会使代码变的更加可读。
在使用mybatis作为数据库持久层时,经常会自定义处理数据库int类型和enum映射关系的typehandler。