Java_19_枚举(enum)
2018-03-07 19:42
393 查看
枚举是软件开发中使用率非常高的类型。这里针对枚举进行一次深入的讨论,希望对您有所帮助。
关于枚举就讨论到这里,欢迎留言讨论。
枚举的使用
Java 中的枚举是一个比较特殊的类型,既具有 class 的特性,又具有自己特殊的特性。定义枚举类型使用 enum 关键字,枚举值一般使用大写字母,如下所示。使用枚举类型的 name() 方法可以获取字符串的名称,使用 ordinal() 方法可以获取枚举值的下标,这里不做赘述。enum SexOne { MALE,FEMALE }枚举同样可以拥有构造器和变量,但枚举类型的构造器要求必须是 private 类型。这是为了确保枚举的值一定由自己定义,拒绝外界传入。与 class 不同的是,枚举类型的构造器不定义访问权限时,默认为 private。
enum SexTwo { MALE("man"), FEMALE("woman"); String alias; SexTwo(String alias){ this.alias = alias; } }枚举类型自动拥有 values 和 valueOf 方法,values 用于获取枚举所有的值,valueOf 用于根据名称反查枚举值。在后面的实现原理中,我们将看到这两个方法的实现。
枚举的实现原理
枚举类型编译后生成一个 class 并且继承 Enum 类型(敲黑板,划重点,一定要记住)。反编译可以看到 SexTwo 的字节码,对字节码进行还原可以得到如下的 class。final class SexTwo extends Enum{ public static SexTwo[] values(){ return (SexTwo[])$VALUES.clone(); } public static SexTwo valueOf(String s){ return (SexTwo)Enum.valueOf(SexTwo, s); } private SexTwo(String s, int i, String s1){ super(s, i); alias = s1; } public static final SexTwo MALE; public static final SexTwo FEMALE; String alias; private static final SexTwo $VALUES[]; static { MALE = new SexTwo("MALE", 0, "man"); FEMALE = new SexTwo("FEMALE", 1, "woman"); $VALUES = (new SexTwo[] { MALE, FEMALE }); } }SexTwo 编译后变成了一个普通的 class 类型。这里需要注意的是,SexTwo 拥有修饰符 final,因此不能有子类。同时继承了 Enum 类型,因此开发中 SexTwo 也将不能继承任何父类。SexTwo 生成的构造器是 private 类型,有两个 public static final SexTwo 类型的变量 MALE 和 FEMALE。编译时编译器自动根据枚举值的顺序确定下标,并在创建枚举值时使用。枚举类型定义的值都是 public static final 类型,在静态初始化中进行初始化。这里可以看到 SexTwo 在静态初始化中为变量 MALE、FEMALE 进行赋值,使用的参数就是我们给定的参数 man 和 woman。
自动生成的 values 和 valueOf
Java 编译枚举类型时,自动加上两个静态方法 values 和 valueOf。如果我们定义了签名完全相同的方法会编译报错 “已在枚举中定义了方法 values”。枚举类型使用私有静态变量 $VALUES 存储所有的值,在静态初始化中赋值,类型为数组,值为所有枚举值,用于 values 和 valueOf 方法。values 方法的作用是返回所有枚举值,实现很简单,就是 clone 一下 $VALUES 的值。valueOf 方法的作用是根据枚举值的名称返回枚举值,实现方法是调用 Enum.valueOf 方法,后文会在 Enum 类型中介绍这个方法。Enum 类型
Enum 类型是 Java 中所有枚举类型的父类,并且是抽象类。需要注意的是我们不能直接继承 Enum 类,只有编译器生成的枚举最终的 class 可以继承,直接继承会导致编译器报错 “类无法直接扩展 java.lang.Enum”。Enum 类型提供了我们常用的 name() 和 ordinal() 方法,其中的 name 和 ordinal 变量都是 final 类型。private final String name; public final String name() { return name; } private final int ordinal; public final int ordinal() { return ordinal; }Enum 拥有一个构造器,参数为 name 和 ordinal。经过前面的分析可以知道,这个构造器我们是没有办法直接调用的,只有编译器编译的枚举类型可以调用。
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }Enum 定义了一个 valueOf 方法,用于枚举类型调用。可以看到 SexTwo 中的 valueOf 方法就是调用 Enum.valueOf(SexTwo,s) 实现的。
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); }Enum 中的大部分方法都是 final 类型的,因此枚举类型是没有办法覆盖这些方法的,唯一能够覆盖的就是 toString 方法。
枚举 与 Class
前面我们已经看到,values 方法是编译器自动加到枚举类型上的,而 Enum 类型并没有提供 values 方法,也就是说我们把枚举类型向上转型为 Enum 类型后没法调用 values 方法。或者我们可能需要传递 Class 对象进行反射操作,这种情况下如何操作枚举呢?Class 对象的 isEnum() 方法可以用来判断是否是枚举类型,如果是枚举类型该方法返回 true。Class 对象的 getEnumConstants() 方法可以用来获取所有枚举值。Enum e = SexTwo.MALE; Class clazz = e.getDeclaringClass(); if(clazz.isEnum()){ SexTwo[] constants = (SexTwo[]) clazz.getEnumConstants(); }
枚举使用抽象方法
前面提到了枚举类型编译之后是以 class 方式运行的,因此枚举类型也可以定义抽象方法。有同学可能要问了,前面说是 final class 类型,怎么可以定义抽象方法呢,抽象方法需要类型是 abstract class ?我们来试一下。如下,SexThree 定义了一个抽象方法 getAlias()。枚举值 MALE 实现了这个抽象方法,返回 "man"。在枚举中定义抽象方法,可以为多个枚举值定义不同的实现,枚举值自身即可处理数据。enum SexThree { MALE { String getAlias() { return "man"; } }; abstract String getAlias(); }对 SexThree 进行编译和反编译,可以看到,这里生成的类型是抽象类,仍然继承了 Enum。MALE 在编译后生成了 SexThree$1.class,继承 SexThree 并实现了抽象方法。虽然这里枚举类型是抽象类,但是 java 编译器限制了我们不能继承这个抽象类,继承情况下编译时会报错“枚举类型不可继承”。
abstract class SexThree extends Enum { public static SexThree[] values() { return (SexThree[])$VALUES.clone(); } public static SexThree valueOf(String s) { return (SexThree)Enum.valueOf(test/SexThree, s); } private SexThree(String s, int i) { super(s, i); } abstract String getAlias(); public static final SexThree MALE; private static final SexThree $VALUES[]; static { MALE = new SexThree("MALE", 0) { String getAlias() { return "man"; } }; $VALUES = (new SexThree[] { MALE }); } }
枚举实现接口
枚举最终编译成 class,因此也可以实现接口。如下的 SexFour 就实现了 Runnable 接口,定义了 run 方法。因为枚举不能继承其他类型,这个特性使得我们可以通过实现多个方法来定义枚举的某些特征。enum SexFour implements Runnable { ; public void run() { //something } }对 SexFour 进行编译和反编译,可以看到,这里又生成了 final class 类型,并且继承了 Enum,实现了 Runnable 接口。
final class SexFour extends Enum implements Runnable{ public static SexFour[] values(){ return (SexFour[])$VALUES.clone(); } public static SexFour valueOf(String s){ return (SexFour)Enum.valueOf(test/SexFour, s); } private SexFour(String s, int i){ super(s, i); } public void run(){ } private static final SexFour $VALUES[] = new SexFour[0]; }
EnumMap
EnumMap 是一个使用枚举值做 key 的 Map 实现。如下代码即可创建 EnumMap 的实例,创建时需要指定 key 类型的 class,或者传入一个 map 进行初始化。EnumMap 使用数组存储数据,效率高于 HashMap。//创建一个具有指定键类型的空枚举映射 EnumMap<SexThree,String> map1=new EnumMap<SexThree,String>(SexThree.class); //从一个 EnumMap 创建 EnumMap<SexThree,String> map2 = new EnumMap<SexThree,String>(map1); //从一个 Map 创建 Map<SexThree, ? extends String> map3 = new HashMap<>(); EnumMap<SexThree,String> map4 = new EnumMap<SexThree, String>(map3);EnumMap 使用枚举值做 key,因此选择了枚举值的下标作为值的下标,把值存储在一个数组中。这样显著提高了数据存取效率,但是容量不能发生变化,适合于数据量比较固定又需要较高效率的场景。
public V get(Object key) { return (isValidKey(key) ? unmaskNull(vals[((Enum<?>)key).ordinal()]) : null); } public V put(K key, V value) { typeCheck(key); int index = key.ordinal(); Object oldValue = vals[index]; vals[index] = maskNull(value); if (oldValue == null) size++; return unmaskNull(oldValue); }
EnumSet
EnumSet 是一个枚举集合,是一个抽象类,它有两个继承类:JumboEnumSet和 RegularEnumSet。EnumSet 使用 bit 位存储数据,效率高于 HashSet。//创建一个包含指定元素类型的所有元素的枚举 set EnumSet<SexTwo> setAll = EnumSet.allOf(SexTwo.class); //创建一个指定范围的Set EnumSet<SexTwo> setRange = EnumSet.range(SexTwo.MALE,SexTwo.FEMALE); //创建一个指定枚举类型的空set EnumSet<SexTwo> setEmpty = EnumSet.noneOf(SexTwo.class); //复制一个set EnumSet<SexTwo> setNew = EnumSet.copyOf(setRange);从 JumboEnumSet 的 add 方法可以一窥 EnumSet 的实现原理。首先把枚举值的下标值无符号右移 6 位,也就是按照 64 位进行分组,找到当前分组的数值。然后,按照枚举值的下标值进行左移,找到添加的位置,把该位置置为 1。同样地,EnumSet 的容量也不能发生变化,枚举类型的定义决定了 EnumSet 的固定容量值。
public boolean add(E e) { typeCheck(e); int eOrdinal = e.ordinal(); int eWordNum = eOrdinal >>> 6; long oldElements = elements[eWordNum]; elements[eWordNum] |= (1L << eOrdinal); boolean result = (elements[eWordNum] != oldElements); if (result) size++; return result; }
关于枚举就讨论到这里,欢迎留言讨论。
相关文章推荐
- 枚举(enum)类型是Java 5新增的特性,它是一种新的类型,允许用常量来表示特定的数据片断,而且全部都以类型安全的形式来表示。
- 关于Java中枚举Enum的深入剖析
- Java 枚举(enum) 详解7种常见的用法
- JAVA基础加强:枚举(Enum)
- Java enum枚举 测试例子
- 黑马程序员Java类之枚举(Enum)
- Java学习笔记21 枚举类型enum、Enu…
- 161208、Java enum 枚举还可以这么用
- java enum(枚举)使用详解 + 总结
- Java中自定义枚举(Enum)项的值及int和Enum的互相转换
- Java 枚举(enum) 详解7种常见的用法
- java 枚举 Enum
- java enum(枚举)理解
- Java 枚举(enum) 详解7种常见的用法
- Java中自定义枚举(Enum)项的值,可设置为指定的值
- Java 枚举(enum) 详解7种常见的用法
- java enum(枚举)使用详解 + 总结
- java enum(枚举)使用详解 + 总结
- java中关于枚举enum类型的用法
- Java 枚举(enum) 详解7种常见的用法(转)