Java Enum枚举

Java Enum枚举,枚举的定义及使用、枚举与类的区别、枚举的本质、java.lang.Enum抽象类

Enum枚举

在 jdk1.5 之前,如果需要定义一个表示星期几的常量,一般通过public static final ...来定义:

使用 enum 关键字进行枚举的定义:

1) Week 类(枚举)的反编译分析:

  1. final class Week extends java.lang.Enum<Week>:枚举 Week 实际上是一个继承自 Enum 的 final 类;
    因此,枚举类型不能再继承其他基类,当然也不能被继承。但是枚举类型可以实现(一个或多个)接口;
    注意,我们自己不能显式定义继承自 Enum 抽象基类的派生类,会产生一个编译错误,Enum 类只能由编译器调用;
  2. 编译器自动为 Week 枚举定义了相应的public static final Week静态常量,也就是说,类似Week.Fri其实是一个引用类型!
  3. public static Week[] values()返回 Week 枚举的所有取值的数组,其调用Object.clone()方法拷贝常量$VALUES
  4. public static Week valueOf(java.lang.String)返回字符串对应的枚举常量,内部通过调用Enum.valueOf(Class<T> enumType, String name)方法来实现;
  5. private Week(String name, int ordinal)私有构造函数,内部通过super(name, ordinal)实现,name 为 枚举常量的字符串形式,ordinal 为序号(默认从 0 开始编号);
    Enum 类的唯一构造函数protected Enum(String name, int ordinal)
    如果我们需要显式定义枚举类型的构造函数,那么其访问性必须为private或者[default]
    枚举类型的构造函数只能由编译器来调用,否则产生编译错误;并且构造函数内部不能显式调用super(name, ordinal)基类的构造函数!
  6. 最后是一个 static 静态初始块,例如对于 Week.Fri,Fri = new Week("Fri", 5)

2) Main$1 匿名类的反编译分析:
我们知道,switch...case只能接受整型字面量字符/字符串字面量、以及对应的 final 常量;
而 Week.Fri 是一个引用变量,不属于上面的任意一种,所以需要进行相应的修改才能编译通过;
于是编译器将对应的枚举常量替换为它们的 ordinal 序号(int 整型),这时候就没有任何问题了;

Enum抽象类

通过上面的简单分析,我们得知,使用 enum 定义的枚举类型其实就是一个实实在在的类,基本可以把它当作类来使用(不过有一些特别的限制);

它们都共同继承自java.lang.Enum抽象基类,Enum 类的常用方法:

需要注意的是,java.lang.Enum 类不能被显式的继承,只能由编译器使用,下面的例子演示了这种情况;

Enum.getClass() 和 Enum.getDeclaringClass() 的区别在哪里?
1) getClass()方法:继承自 Object 类,返回一个 Class 类对象(类型信息);
2) getDeclaringClass()方法:Enum 类定义的 public 方法,返回枚举常量对应的枚举类型的 Class 对象;

Stack Overflow 提问帖(已有答案)地址

讨论的结果:
1) 对于没有定义匿名类的枚举类型,这两个方法没有区别;比如:

2) 对于定义了匿名类的复杂枚举,getClass() 始终返回当前对象对应的类的 Class 对象,getDeclaringClass() 返回枚举常量所属的枚举类型的 Class 对象;比如:

3) 因此,如果要获取枚举类型的 Class 对象,请统一使用 getDeclaringClass() 方法;

values()方法、valueOf()方法
这里指的是由 enum 关键字定义的枚举类型内自动生成的 values()、valueOf() 方法;
在 Enum 基类中并没有定义 values() 方法,只有在具体的枚举类中才有,它返回枚举类中所有的枚举常量集合(数组);
在 Enum 基类中定义了 valueOf() 方法,但是它接受两个参数public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name),在具体的枚举类中的 valueOf() 只有一个 String 参数即枚举的字符串形式,本质还是调用的 Enum.valueOf() 方法;

使用例子:

但是,如果将 Week 向上转型为 Enum 类型,那么就不能像这样直接调用 values() 方法了;
但是可以通过 Class 类的public T[] getEnumConstants();方法间接获取,public boolean isEnum();方法判断是否为 Enum 类型;例子:

Enum进阶用法

自定义 private/[default] 构造函数,需要注意的几点是:
1) 访问修饰符只能为private默认
2) 不能在构造函数内部调用super(...)基类的构造函数;
3) 必须先定义完枚举常量后才能自定义构造函数及其他成员,否则编译报错;

定义枚举常量的内部方法(实则为一个内部匿名类)

enum 枚举类型还可以实现接口;比如:

EnumMap、EnumSet

EnumMap
1) EnumMap 是枚举的专属的 Map 键值对集合(key 必须为 Enum 类型);
2) EnumMap 因为其内部是通过数组实现的,所以相对来说效率比普通的 HashMap 高;
3) EnumMap 虽说是枚举专属集合,但其操作与一般的 Map 差不多;

例子:

EnumSet
1) EnumSet 是与枚举类型一起使用的专用 Set 集合,EnumSet 中所有元素都必须是枚举类型;
2) EnumSet 与其他 Set 接口的实现类 HashSet/TreeSet(内部都是用对应的 HashMap/TreeMap 实现的)不同的是,EnumSet 在内部实现是位向量,它是一种极为高效的位运算操作,由于直接存储和操作都是 bit,因此 EnumSet 空间和时间性能都十分可观,足以媲美传统上基于 int 的“位标志”的运算;
3) EnumSet 不允许使用 null 元素;试图插入 null 元素将抛出 NullPointerException,但试图测试判断是否存在 null 元素或移除 null 元素则不会抛出异常
4) 与大多数 collection 实现一样,EnumSet 不是线程安全的;

创建 EnumSet 并不能使用 new 关键字,因为它是个抽象类,而应该使用其提供的静态工厂方法,常用的几个如下:
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType):创建一个空 Set;
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType):创建一个包含所有枚举常量的 Set;
public static <E extends Enum<E>> EnumSet<E> range(E from, E to):创建一个指定范围的 Set;

例子: