# 简介
java中的枚举一直被认为比较强大的, 有很多其他语言枚举没有的特性, 这里我们就来深入探索下
Java中的枚举
# 如何创建一个枚举
# enum关键字定义枚举
//简单定义枚举
public enum WeekDayEnum {
Mon, Tue, Wed, Thu, Fri, Sat, Sun
}
# 定义enum属性和方法
public enum WeekDayEnum {
Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6), Sun(7);
private WeekDayEnum(int romanNumeral){
this.romanNumeral=romanNumeral;
}
//给enum定义属性
//private属性
private int romanNumeral;
//public属性
public int order;
//定义enum方法
public int getRomanNumeral(){
return this.romanNumeral;
}
}
# 枚举类如何实现的
我们来一步一步说明枚举类的特性
# 反编译枚举类
接下来我们先反编译上面例子中的枚举类, 得到的内容如下
$ javap -p WeekDayEnum.class
Compiled from "WeekDayEnum.java"
public final class cn.jessex.console.WeekDayEnum extends java.lang.Enum<cn.jessex.console.WeekDayEnum> {
public static final cn.jessex.console.WeekDayEnum Mon;
public static final cn.jessex.console.WeekDayEnum Tue;
public static final cn.jessex.console.WeekDayEnum Wed;
public static final cn.jessex.console.WeekDayEnum Thu;
public static final cn.jessex.console.WeekDayEnum Fri;
public static final cn.jessex.console.WeekDayEnum Sat;
public static final cn.jessex.console.WeekDayEnum Sun;
private int romanNumeral;
public int order;
private static final cn.jessex.console.WeekDayEnum[] $VALUES;
public static cn.jessex.console.WeekDayEnum[] values();
public static cn.jessex.console.WeekDayEnum valueOf(java.lang.String);
private cn.jessex.console.WeekDayEnum(int);
public int getRomanNumeral();
static {
//实例化枚举实例
Mon = new WeekDayEnum(1);
Tue = new WeekDayEnum(2);
Wed = new WeekDayEnum(3);
Thu = new WeekDayEnum(4);
Fri = new WeekDayEnum(5);
Sat = new WeekDayEnum(6);
Sun = new WeekDayEnum(7);
$VALUES = (new WeekDayEnum[] {
Mon, Tue, Wed, Thu, Fri, Sat, Sun
});
};
}
# 从反编译可以看出
# 枚举编译后也是class
# 枚举是final class, 无法被再继承
# 枚举默认继承java.lang.Enum
# 每一个枚举值都是一个枚举类的实例对象
# 枚举类的实例对象是由public static final修饰
# 枚举类的构造器是私有的
# 默认帮你实现了2个方法:values()和valueOf(T t)
# 默认定义了$VALUES枚举类数组, 并初始化为包含所有实例对象
# 静态代码块中进行初始化枚举实例对象以及初始化枚举数组$VALUES
# 枚举不能继承其他类
根据反编译可以看到, 枚举类其实也是一个普通的class, 是在编译阶段, 会将enum转换成class类, 并且此class会继承java.lang.Enum
类, 因此枚举类不能再继承其他类(Java类规定只能继承一个类)
# 枚举可以实现多个接口
同样根据反编译结果, 知道枚举实现多个接口肯定是可以的.
# 枚举类的默认构造器必须私有
否则就允许再构造新的实例对象, 违背了枚举的意义.
# 默认提供values()
//编译器为我们添加的静态的values()方法
public static WeekDayEnum[] values() {
return (WeekDayEnum[])$VALUES.clone();
}
# 默认提供valueOf(T t)
//编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum类的valueOf方法
public static WeekDayEnum valueOf(String s){
return (WeekDayEnum)Enum.valueOf(cn/jessex/console/WeekDayEnum, s);
}
# 探索枚举父类(java.lang.Enum)
# 实现了Comparable和Serializable接口
# 只提供了一个构造方法
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
# 重写了Object的clone(),equals(),finalize(),hashCode(),toString()方法
# equals()方法
用来判断是否为同一个枚举实例值
# 实现了Comparable接口的compareTo(E e)方法
父类
Enum默认实现的需要注意返回值是
self.ordinal - 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;
}
# 定义了两个私有变量
//序数
private final int ordinal;
//
private final String name;
# 提供了对应获取私有变量的两个方法
//返回枚举实例声明时的次序, 从0开始
public final int ordinal() {
return ordinal;
}
//返回枚举实例的名字
public final String name() {
return name;
}
# 提供了values()方法
由编译器生成
返回 enum 实例的数组,而且该数组中的元素严格保持在 enum 中声明时的顺序
# 提供方法valueOf()
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);
}
# 提供方法getDeclaringClass()
//返回枚举实例所属的enum类型, 也就是返回类的类型
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
# 提供了两个防止默认反序列化的私有方法
/**
* prevent default deserialization
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
# 枚举类的用法
# 实现接口,固定行为
# 枚举类实现接口
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour{
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//接口方法
@Override
public String getInfo() {
return this.name;
}
//接口方法
@Override
public void print() {
System.out.println(this.index+":"+this.name);
}
}
# 枚举类实现带泛型的接口
//在接口上管理和规范枚举的实现
public interface IPairs<K,V,C extends Enum> {
C get();
K key();
V value();
}
enum Status implements IPairs<Integer, String, Status> {
DISABLED(0, "record has been disabled"),
ENABLED(1, "record has been enabled"),
DELETES(9, "record has been deleted");
private Integer key;
private String value;
private Status(Integer key, String value){
this.key = key;
this.value = value;
}
@Override
public Status get() {
return this;
}
@Override
public Integer key() {
return this.key;
}
@Override
public String value() {
return this.value;
}
}
# 在接口中定义枚举类
public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}
# 枚举类中定义枚举, 组织和管理枚举类
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数组中
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;
}
}
public Food randomSelection() {
//随机返回一个枚举值
return Enums.random(values);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
for (Meal meal : Meal.values()) {
Food food = meal.randomSelection();
System.out.println(food);
}
System.out.println("---");
}
}
}
下面是随机取枚举值的工具方法
public class Enums {
private static Random rand = new Random(12);
public static <T extends Enum<T>> T random(Class<T> ec) {
return random(ec.getEnumConstants());
}
public static <T> T random(T[] values) {
return values[rand.nextInt(values.length)];
}
}
# 在枚举类中定义抽象方法
枚举类中定义抽象方法后, 枚举类编译后会被声明成
abstract抽象类, 如果有多个枚举实例值则会生成多个.class文件, 通常命名为xxx$1.class,xxx$2.class.
public enum StatusEnum {
/**
* EXCEPTION
*/
EXCEPTION("450", "异常","3","0"){
@Override
public String getDesc() {
return "订单发生异常";
}
},
/**
* CANCELLATION
*/
CANCELLATION("500", "已作废","3","1"){
@Override
public String getDesc() {
return "订单已作废";
}
};
private String value;
private String key;
private String location;
private String isReturn;
StatusEnum(String value, String name, String location,String isReturn) {
this.value = value;
this.key = name;
this.location = location;
this.isReturn = isReturn;
}
public String getName(){
return this.key;
}
//每个枚举实例都需要实现定义的抽象方法
public abstract String getDesc();
public static void main(String[] args) {
System.out.println(StatusEnum.EXCEPTION.getDeclaringClass());
System.out.println(StatusEnum.CANCELLATION.ordinal());
System.out.println(StatusEnum.EXCEPTION.equals(StatusEnum.EXCEPTION));
System.out.println(StatusEnum.EXCEPTION.equals(Status.DELETES));
}
}
# 策略枚举的使用
public enum PayrollDay {
MONDAY(PayType.WEEKDAY),
TUESDAY(PayType.WEEKDAY),
WEDNESDAY(PayType.WEEKDAY),
THURSDAY(PayType.WEEKDAY),
FRIDAY(PayType.WEEKDAY),
SATURDAY(PayType.WEEKEND),
SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) {
this.payType = payType;
}
double pay(double hoursWorked, double payRate) {
return payType.pay(hoursWorked, payRate);
}
// 策略枚举
private enum PayType {
WEEKDAY {
@Override
double overtimePay(double hours, double payRate) {
return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)
* payRate / 2;
}
},
WEEKEND {
@Override
double overtimePay(double hours, double payRate) {
return hours * payRate / 2;
}
};
private static final int HOURS_PER_SHIFT = 8;
abstract double overtimePay(double hrs, double payRate);
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}
# 使用枚举实现单例模式
# 枚举工具类
# Java自带的EnumSet
EnumSet 是枚举类型的高性能 Set 实现。它要求放入它的枚举常量必须属于同一枚举类型。
# Java自带的EnumMap
EnumMap 是专门为枚举类型量身定做的 Map 实现。虽然使用其它的 Map 实现(如 HashMap)也能完成枚举类型实例到值得映射,但是使用 EnumMap 会更加高效:它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值。这使得 EnumMap 的效率非常高。
# 自造轮子
# 根据对应的变量获取枚举实例
//本示例代码是根据name字段获取对应的枚举
//在泛型上设定了上限为BaseEnum, 里面包含了抽象方法getName()
public static <T extends BaseEnum> T getByName(Class<T> enumClass, String name) {
for (T each: enumClass.getEnumConstants()) {
//通过抽象方法判断是否相等
if (each.getName().equals(name)) {
return each;
}
}
return null;
}
# 缺点
对于Android这种需要极致的优化内存占用的情况, 因为enum会比其他方式占用更多的内存.