in java enum 枚举 EnumSet EnumMap 动态添加枚举 ~ read.

Java中的枚举

本文基于JDK 1.8.0_45


一个简单的枚举例子

public enum EnumExample {  
    enum1, enum2;
}

以上是一个简单的枚举例子,我们通过关键字enum定义了一个名叫EnumExample的枚举类,在其中我们定义了两个枚举类型:enum1和enum2。

在Java中所有的枚举类默认都会继承自Enum抽象类,所有的枚举类型都会自动包含Enum抽象类中定义的方法。其中包含了获取枚举类型的名字、顺序、所属的类,另外还有通过枚举类和名字获取枚举类型的静态方法,如下所示:

public enum EnumExample {  
    enum1, enum2;

    public static class Test {
        public static void main(String[] args) {
            System.out.println(enum1.name());// enum1
            System.out.println(enum1.ordinal());// 0
            System.out.println(enum1.getDeclaringClass().getSimpleName());// EnumExample

            System.out.println(enum2.name());// enum2
            System.out.println(enum2.ordinal());// 1
            System.out.println(enum2.getDeclaringClass().getSimpleName());// EnumExample

            for (int i = 0; i < EnumExample.values().length; i++) {
                System.out.printf("%s is %s; ", i, EnumExample.values()[i]);// 0 is enum1; 1 is enum2;
            }
            System.out.println();

            System.out.println(EnumExample.valueOf("enum1") == enum1);// true
            System.out.println(Enum.valueOf(EnumExample.class, "enum1") == enum1);// true
        }
    }
}

其中valueOf和values两个方法是Java在编译期添加到枚举类中的方法,如例中所示,用来提供便利的获取所有枚举类型或某一个特殊的枚举类型。

给枚举添加自定义方法

如下例所示我们定义了一个枚举类Fruit,并给每一种枚举类型添加了一个属性color,最后我们定义了一个方法getColor让用户可以从枚举类型中获取对应的color。

public enum Fruit {  
    Apple("red"),
    Orange("yellow");

    private String color;

    Fruit(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }
}

实现接口的枚举

如下例所示我们定义了一个枚举类Fruit并实现了food接口:

public interface Food {  
    String getColor();
}

public enum Fruit implements Food {  
    Apple("red"),
    Orange("yellow");

    private String color;

    Fruit(String color) {
        this.color = color;
    }

    @Override
    public String getColor() {
        return color;
    }
}

在switch中使用枚举

以下是在switch中使用枚举的例子:

public class EnumExample {  
    public enum Fruit {
        Apple, Orange;
    }

    private static Fruit fruit;

    public static void main(String[] args) {
        switch (fruit) {
            case Apple:
                System.out.println("I'm apple");
                break;
            case Orange:
                System.out.println("I'm orange");
                break;
            default:
                System.out.println("I don't know what I am");
                break;
        }
    }
}

为什么switch中可以使用枚举呢?查看字节码可知,第10行说明枚举在switch中被编译器编译为了1、2,由此可知,Java是通过将枚举类型转换为整数来实现switch的。

➜  javap -verbose EnumExample
         0: invokestatic  #20                 // Method $SWITCH_TABLE$com$oomlife$java$example$EnumExample$Fruit:()[I
         3: getstatic     #23                 // Field fruit:Lcom/oomlife/java/example/EnumExample$Fruit;
         6: invokevirtual #25                 // Method com/oomlife/java/example/EnumExample$Fruit.ordinal:()I
         9: iaload
        10: tableswitch   { // 1 to 2
                       1: 32
                       2: 43
                 default: 54
            }
        32: getstatic     #31                 // Field java/lang/System.out:Ljava/io/PrintStream;
        35: ldc           #37                 // String I'm apple
        37: invokevirtual #39                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        40: goto          62
        43: getstatic     #31                 // Field java/lang/System.out:Ljava/io/PrintStream;
        46: ldc           #45                 // String I'm orange
        48: invokevirtual #39                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        51: goto          62
        54: getstatic     #31                 // Field java/lang/System.out:Ljava/io/PrintStream;
        57: ldc           #47                 // String I don't know what I am
        59: invokevirtual #39                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        62: return

EnumMap

EnumMap的特点如下:

  1. EnumMap是使用枚举作为key的Map的实现;
  2. 它内部是一个数组,长度为所使用的枚举类中所有的枚举类型数目;
  3. 数组内容的顺序是枚举类型在枚举类中的顺序;
  4. 所有的操作都是常量时间,因此在枚举是key的时候一定程度上要比HashMap快;
  5. 由于默认枚举类在定义后不会发生变化,因此EnumMap没有考虑扩容的问题;
  6. 线程不安全;

EnumSet

EnumSet的特点如下:

  1. EnumSet是使用枚举作为key的Set的实现;
  2. 它内部是一个bit vectors,因此具有很好的压缩和效率;
  3. 内容的顺序是枚举类型在枚举类中的顺序;
  4. 所有的操作都是位操作,是常量时间,因此在枚举是key的时候一定程度上要比HashSet快;
  5. 由于默认枚举类在定义后不会发生变化,因此EnumSet没有考虑扩容的问题;
  6. 线程不安全;

EnumSet内部是通过两个实现:RegularEnumSet和JumboEnumSet,前者内部有一个long型数字,后者内部有一个long的数组。由于EnumSet内部是通过long型的数字来作为bit vector的,因此当枚举类型少于或等于64的时候使用RegularEnumSet,大于64的时候使用JumboEnumSet。

EnumSet中提供了很多的工具方法来生成EnumSet:

public class EnumExample {  
    public enum Fruit {
        Apple, Orange, Banana, Pear;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(EnumSet.allOf(Fruit.class));// [Apple, Orange, Banana, Pear]

        System.out.println(EnumSet.noneOf(Fruit.class).size());// 0

        System.out.println(EnumSet.of(Fruit.Apple, Fruit.Orange));// [Apple, Orange]

        System.out.println(EnumSet.copyOf(EnumSet.of(Fruit.Orange, Fruit.Banana)));// [Orange, Banana]

        System.out.println(EnumSet.copyOf(Arrays.asList(Fruit.Apple, Fruit.Apple, Fruit.Banana)));// [Apple, Banana]

        System.out.println(EnumSet.complementOf(EnumSet.of(Fruit.Orange, Fruit.Banana)));// [Apple, Pear]

        System.out.println(EnumSet.range(Fruit.Orange, Fruit.Pear));// [Orange, Banana, Pear]
    }
}

运行期动态添加一种新的枚举类型

在一些特殊场景下可能需要在运行期动态添加一种新的枚举类型,下面是一个例子:

public class EnumExample {  
    public enum Fruit {
        Apple, Orange;
    }

    public static void main(String[] args) throws Exception {
        System.out.println("Before adding enum type...");
        printAllEnumType(Fruit.class);

        addEnumType(Fruit.class, "Banana");

        System.out.println("After adding enum type...");
        printAllEnumType(Fruit.class);
    }

    private static void printAllEnumType(Class<? extends Enum<?>> enumClass) {
        for (int i = 0; i < enumClass.getEnumConstants().length; i++) {
            System.out.printf("%s is %s; ", i, enumClass.getEnumConstants()[i]);
        }
        System.out.println();
    }

    @SuppressWarnings("unchecked")
    private static <T extends Enum<?>> void addEnumType(Class<T> enumClass, String enumName) throws Exception {
        if (!Enum.class.isAssignableFrom(enumClass)) {
            throw new IllegalArgumentException("Enum class should be enum");
        }

        // 反射获取枚举类中保存所有枚举类型的数组,从字节码中可以找到该成员变量
        Field field = enumClass.getDeclaredField("ENUM$VALUES");
        field.setAccessible(true);
        T[] values = (T[]) field.get(enumClass);

        T[] newValues = Arrays.copyOf(values, values.length + 1);

        // 创建新的枚举类型
        T newValue = createEnumType(enumClass, enumName, values.length);
        newValues[newValues.length - 1] = newValue;

        // 将枚举类中的枚举类型数组设置为新的枚举类型的数组
        setEnumValue(field, enumClass, newValues);

        // 清除枚举缓存
        clearEnumCache(enumClass);
    }

    private static final ReflectionFactory reflectionFactory = AccessController.doPrivileged(new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());

    @SuppressWarnings("unchecked")
    private static <T extends Enum<?>> T createEnumType(Class<T> enumClass, String enumName, int ordinal) throws Exception {
        // 不能使用java.lang.reflect包的反射方法,因为java.lang.reflect.Constructor不让反射创建枚举对象
        ConstructorAccessor constructor = reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(String.class, Integer.TYPE));
        return (T) constructor.newInstance(new Object[]{enumName, ordinal});
    }

    private static <T extends Enum<?>> void setEnumValue(Field field, Object target, Object newValues) throws Exception {
        field.setAccessible(true);

        if (Modifier.isFinal(field.getModifiers())) {
            int modifiers = field.getModifiers();

            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            // 将field取消final的修饰
            modifiersField.set(field, modifiers & ~Modifier.FINAL);
        }

        // 不能使用java.lang.reflect包的反射方法,因为java.lang.reflect.Field不让反射修改枚举对象或final对象
        reflectionFactory.newFieldAccessor(field, false).set(target, newValues);
    }

    private static <T extends Enum<?>> void clearEnumCache(Class<T> enumClass) throws Exception {
        setEnumValue(Class.class.getDeclaredField("enumConstants"), enumClass, null);
        setEnumValue(Class.class.getDeclaredField("enumConstantDirectory"), enumClass, null);
    }
}


打印信息:
Before adding enum type...  
0 is Apple; 1 is Orange;  
After adding enum type...  
0 is Apple; 1 is Orange; 2 is Banana;  

请注意该代码仅供学习,如要使用在生产环境,请自行添加异常处理等额外代码。

  1. 首先获取枚举类的成员变量ENUM$VALUES的值,该成员变量是保存所有的枚举类型;
  2. 由于所有的枚举类都自动继承自Enum类,所以可以通过Enum类的构造方法反射创建一个新的枚举类型,入参为name和ordinal;
  3. 将1和2的值合并为一个新的数组并设置为枚举类的成员变量ENUM$VALUES的值;
  4. 清楚枚举类的Class实例中的枚举相关的缓存,这些缓存是存放在Class的成员变量enumConstants和enumConstantDirectory中,前者是数组类型的缓存,后者是Map类型的缓存;
  5. 新的枚举类型就被动态添加到了指定的枚举类中;
分享按钮