转载自:https://blog.csdn.net/javazejian/article/details/71333103#t17
枚举实现原理
//枚举类型,使用关键字enum
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
编译上面代码:javac Day.java 会生成一个class文件,继承java.lang.Enum类
//反编译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的(不能被继承),并且extends Enum(所以不能再继承其他类,可以实现多个接口)。
- 有几个定义好的static final的实例对象。
- 静态values()和valueOf()方法。
Enum抽象类
主要方法:
- int compareTo(E o):两对象比较(比较的是orordinal值)
- boolean equals(Object other):当指定对象等于此枚举常量时,返回 true。
- Class<?> getDeclaringClass():返回与此枚举类型相对应的class对象
- String name():枚举常量的名称
- int ordinal():序数,从0开始。大多数情况下不使用这个,变化莫测的。
- String toString():枚举常量的名称,和name()一样
- static<T extends Enum<T>> static valueOf(Class<T> enumType, String name):返回带指定名称的枚举常量
Enum主要源码
//实现了Comparable
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);
}
//.....省略其他没用的方法
}
注意:Enum类中没有values()方法,该方法存在于enum Day中。
//正常使用
Day[] ds=Day.values();
//向上转型Enum
Enum e = Day.MONDAY;
//无法调用,没有此方法
//e.values();
Enum与Class对象
方法
- T[] getEnumConstants():返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。
- boolean isEnum():当且仅当该类声明为源代码中的枚举时返回 true
//向上转型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与interface
有时候,我们可能需要对一组数据进行分类,比如进行食物菜单分类而且希望这些菜单都属于food类型,appetizer(开胃菜)、mainCourse(主菜)、dessert(点心)、Coffee等,每种分类下有多种具体的菜式或食品,此时可以利用接口来组织,如下(代码引用自Thinking in Java):
现在我们利用一个枚举嵌套枚举的方式,把前面定义的菜谱存放到一个Meal菜单中,通过这种方式就可以统一管理菜单的数据了。
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;
}
}
}
enum与单例模式
单例模式写法:饿汉式、懒汉式安全模式、双重校验模式、静态内部类
然而,这四种方法都有同样的2个问题:序列化和反射。
序列化:每次反序列化一个序列化的对象实例时都会创建一个新的实例。解决办法:实现Serializable接口,重写readResolve()方法。
反射:解决办法:构造函数中判断只能创建一个实例
枚举可以解决上述问题。why???
序列化:枚举序列化是由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) {
// enumConstantDirectory() 方法返回的是一个map: <枚举对象的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;
到这里我们也就可以看出枚举序列化确实不会重新创建新实例,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)
查看源码分析问题:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
。。。。。
//这里判断Modifier.ENUM是不是枚举修饰符,如果是就抛异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
。。。。。
T inst = (T) ca.newInstance(initargs);
return inst;
}
EnumMap
EnumMap:key值是枚举类型。比普通Map(如HashMap)高效。
- 枚举值作为key,接收同一类型枚举,且key不能重复,不能为null。
- 枚举实例数量固定有限,EnumMap内部使用数组,数组是连续的内存空间,效率高
构造函数EnumMap(Class<K> keyType),需要传递一个类型信息,即Class对象,通过这个参数EnumMap就可以根据类型信息初始化其内部数据结构。
EnumMap实现原理剖析
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
implements java.io.Serializable, Cloneable
{
//Class对象引用
private final Class<K> keyType;
//存储Key值的数组
private transient K[] keyUniverse;
//存储Value值的数组
private transient Object[] vals;
//map的size
private transient int size = 0;
//空map
private static final Enum<?>[] ZERO_LENGTH_ENUM_ARRAY = new Enum<?>[0];
//构造函数
public EnumMap(Class<K> keyType) {
this.keyType = keyType;
// 方法内部最终调用到枚举类型的values方法,values方法返回所有可能的枚举值
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
}
}
分析put方法:
public V put(K key, V value) {
typeCheck(key);//检测key的类型
//获取存放value值得数组下标。能维持与枚举实例相同存储顺序的原因
int index = key.ordinal();
//获取旧值
Object oldValue = vals[index];
//设置value值
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);//返回旧值
}
// 这里通过typeCheck方法进行了key类型检测,判断是否为枚举类型,如果类型不对,会抛出异常
private void typeCheck(K key) {
Class<?> keyClass = key.getClass();//获取类型信息
if (keyClass != keyType && keyClass.getSuperclass() != keyType)
throw new ClassCastException(keyClass + " != " + keyType);
}
//代表NULL值得空对象实例
private static final Object NULL = new Object() {
public int hashCode() {
return 0;
}
public String toString() {
return "java.util.EnumMap.NULL";
}
};
private Object maskNull(Object value) {
//如果值为空,返回NULL对象,否则返回value
return (value == null ? NULL : value);
}
@SuppressWarnings("unchecked")
private V unmaskNull(Object value) {
//将NULL对象转换为null值
return (V)(value == NULL ? null : value);
}
由此看来EnumMap还是允许存放null值的,但key绝对不能为null,对于null值,EnumMap进行了特殊处理,将其包装为NULL对象,毕竟vals[]存的是Object,maskNull方法和unmaskNull方法正是用于null的包装和解包装的。这就是EnumMap集合的添加过程。
get方法:
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
}
//对Key值的有效性和类型信息进行判断
private boolean isValidKey(Object key) {
if (key == null)
return false;
// Cheaper than instanceof Enum followed by getDeclaringClass
Class<?> keyClass = key.getClass();
return keyClass == keyType || keyClass.getSuperclass() == keyType;
}
remove方法:
public V remove(Object key) {
//判断key值是否有效
if (!isValidKey(key))
return null;
//直接获取索引
int index = ((Enum<?>)key).ordinal();
Object oldValue = vals[index];
//对应下标元素值设置为null
vals[index] = null;
if (oldValue != null)
size--;//减size
return unmaskNull(oldValue);
}
contains方法:
判断是否包含某value
public boolean containsValue(Object value) {
value = maskNull(value);
//遍历数组实现
for (Object val : vals)
if (value.equals(val))
return true;
return false;
}
//判断是否包含key
public boolean containsKey(Object key) {
return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null;
}
判断value直接通过遍历数组实现,而判断key就更简单了,判断key是否有效和对应vals[]中是否存在该值。
ok~,这就是EnumMap的主要实现原理,即内部有两个数组,长度相同,一个表示所有可能的键(枚举值),一个表示对应的值,不允许key为null,但允许value为null。键都有一个对应的索引,根据索引直接访问和操作其键数组和值数组,由于操作都是数组,因此效率很高。
EnumSet
待学习。。。。。。