枚举的本质
JVM编译器背地里是这样处理枚举的
- 定义一个继承自Enum类的类,类是用final修饰的。
- 为每个枚举实例对应创建一个类对象,这些类对象是用public static final修饰的。同时生成一个数组,用于保存全部的类对象。
- 生成一个静态代码块,用于初始化类对象和类对象数组。
- 生成一个构造函数,构造函数包含自定义参数和两个默认参数。
- 生成一个静态的values()方法,用于返回所有的类对象。
- 生成一个静态的valueOf()方法,根据name参数返回对应的类实例。
在Enum源代码中,几个值得关注的点
- Enum类有两个成员变量:name和ordinal。其中,name用于记录枚举常量的名字。ordinal用于记录枚举常量在声明时的顺序(从0开始)。
- Enum类有一个构造函数,它有两个入参,分别为name和ordianl赋值。
- Enum类重写了toString()方法,返回枚举常量的name值。
- Enum类重写了equals()方法,直接用等号比较。
- Enum类不允许克隆,clone()方法直接抛出异常。(保证枚举永远是单例的)
- Enum类实现了Comparable接口,直接比较枚举常量的ordinal的值。
- Enum类有一个静态的valueOf()方法,可以根据枚举类型以及name返回对应的枚举常量。
- Enum类不允许反序列化,为了保证枚举永远是单例的。
总结
- 枚举不允许继承类。JVM在生成枚举时已经继承了Enum类,由于Java语言是单继承,不支持再继承额外的类(唯一的继承名额被JVM用了)。
- 枚举允许实现接口。因为枚举本身就是一个类,类是可以实现多个接口的。
- 枚举可以用等号比较。JVM会为每个枚举实例对应生成一个类对象,这个类对象是用public static final修饰的,在static代码块中初始化,是一个单例。
- 不可以继承枚举。因为JVM在生成枚举类时,将它声明为final。
- 枚举本身就是一种对单例设计模式友好的形式,它是实现单例模式的一种很好的方式。
- 枚举类型的compareTo()方法比较的是枚举类对象的ordinal的值。
- 枚举类型的equals()方法比较的是枚举类对象的内存地址,作用与等号等价。
如何对枚举实现代理
枚举肯定是不能用cglib的动态代理了,因为cglib是利用hancer获取代理对象的子类,然而枚举并没有办法被继承,因此此方法行不通。
但枚举可以实现接口,也可以使用JDK的动态代理。
例子:
public enum Month implements IMonthBase {
JANUARY(1, "一月");
private Integer value;
private String name;
Month(Integer value, String name) {
this.value = value;
this.name = name;
}
@Override
public String now() {
return "now";
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
public String getValueByKey(Integer value) {
if (value == null) {
return "";
}
for (Month m: Month.values()) {
if (m.getValue().equals(value)) {
return m.getName();
}
}
return "";
}
}
public class MonthProxy implements InvocationHandler {
private Object target;
public Object getProxyTarget(Object obj) {
this.target = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理前");
Object invoke = method.invoke(target, args);
System.out.println("代理后");
return "pre-" + invoke;
}
public static void main(String[] args) {
IMonthBase month = (IMonthBase) new MonthProxy().getProxyTarget(Month.JANUARY);
System.out.println(month.now());
}
}
能用注解的方式对枚举方法实现代理吗
在使用代理时,我们往往通过注解来定义一些信息,通过method.isAnnotationPresent(Prefix.class) || method.getDeclaringClass().isAnnotationPresent(Prefix.class)
是无法获取注解信息的。但可以通过如下方式获取:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
if (method.isAnnotationPresent(Prefix.class) || method.getDeclaringClass().isAnnotationPresent(Prefix.class)
|| target.getClass().isAnnotationPresent(Prefix.class) || targetMethod.isAnnotationPresent(Prefix.class)) {
Prefix prefix = targetMethod.getAnnotation(Prefix.class);
System.out.println(prefix.preName() + method.invoke(target, args));
}
return "";
}
所以枚举方法也能通过注解实现代理。