# 简介

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会比其他方式占用更多的内存.

# 参考

Java枚举类学习到进阶 (opens new window)

深入理解Java枚举类型(enum) (opens new window)

修改于: 8/11/2022, 3:17:56 PM