关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。
1.基本的enum特性
values()方法返回 enum实例的数组, 而且该数组中的元素严格保持其在enum中声明时的顺序, 因此你可以在循环中使用values()返回的数组。
ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。可以使用==来比较enum实例, 编译器会自动为你提供equals()和hashCode()方法。
1.1 将静态导入用于enum
唯一需要担心的是,使用静态导人会不会导致你的代码令人难以理解。 多数情况下,使用static import还是有好处的不过,程序员还是应该对具体情况进行具体分析。
注意, 在定义enum的同一个文件中,这种技巧无法使用,如果是在默认包中定义enum,这种技巧也无法使用。
2.向enum中添加新方法
如果你打算定义自己的方法,那么必须在enum实例序列的最后添加一个分号。
Java要求你必须先定义enum实例。如果在定义enum实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。
2.1 覆盖enum的方法
覆盖enum的 toSring()方法与覆盖一般类的方法没有区别。
3.switch语句中的enum
在switch中使用enum,是enum提供的一项非常便利的功能。一般来说,在switch中只能使 用整数值,而枚举实例天生就具备整数值的次序,并且可以通过ordinal()方法取得其次序(显 然编译器帮我们做了类似的工作),因此我们可以在switch语句中使用enum。
4.values()的神秘之处
编译器为你创建的enum类都继承自Enum类。Enum类并没有values()方法。
values()是由编译器添加的static方法。
由于values()方法是由编译器插入到enum定义中的static方法,所以, 如果你将enum实例向上转型为Enum, 那么values()方法就不可访问了。 不过,在Class中有一个getEnumConstants()方法, 所以即便Enum接口中没有values()方法,我们仍然可以通过Class对象取得所有enum实例。
5.实现, 而非继承
所有的enum都继承自java.Iang.Enum类。 由千Java不支持多重继承, 所以你的enum不能再继承其他类。
6.随机选取
7.使用接口组织枚举
在一个接口的内部, 创建实现该接口的枚举, 以此将元素进行分组,可以达到将枚举元素分类组织的目的。
8.使用EnumSet替代标志
使用EnumSet的优点是,它在说明个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。
EnumSet的基础是long,一个long值有64位,而一个enum实例只需位bit表示其是否存在。也就是说,在不超过一个long的表达能力的情况下,你的EnumSet可以应用于最多不超过64个元素的enum。
EnumSet可以应用于多过64个元素的enum,Enum会在必要的时候增加一个long
9.使用EnumMap
EnumMap是一种特殊的Map,它要求其中的键(key)必须来自一个enum。由千enum本身的限制,所以EnumMap在内部可由数组实现。因此EnumMap的速度很快,我们可以放心地使用enum实例在EnumMap中进行查找操作。不过,我们只能将enum的实例作为键来调用put()方法,其他操作与使用一般的Map差不多。
与常量相关的方法(constant-specificmethods)相比,EnumMap有一个优点,那EnumMap允许程序员改变值对象,而常量相关的方法在编译期就被固定了。
在你有多种类型的enum,而且它们之间存在互操作的情况下,我们可以用 EnumMap实现多路分发(multiple dispatching)。
10.常量相关的方法
允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。
通过相应的enum实例,我们可以调用其上的方法。 这通常也称为表驱动的代码 (tabledriven code, 请注意它与前面提到的命令模式的相似之处)。
每个enum元素都是一个enum类型的static final实例。同时,由千它们是static实例,无法访问外部类的非static元素或方法,所以对于内部的 enum的实例而言,其行为与一般的内部类井不相同。
10.1 使用enum的职责链
在职责链(Chain of Responsibility)设计模式中,程序员以多种不同的方式来解决一个问题, 然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。
通过常量相关的方法,我们可以很容易地实现一个简单的职责链。我们以一个邮局的模型为例。邮局帣要以尽可能通用的方式来处理每一封邮件,并且要不断尝试处理邮件,直到该邮件枭终被确定为一封死信。其中的每一次尝试可以看作为一个策略(也是一个设计模式),而完整的处理方式列表就是一个职责链。
10.2 使用enum的状态机
枚举类型非常适合用来创建状态机。一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到下一个状态,不过也可能存在瞬时状态(transient states) , 而一且任务执行结束,状态机就会立刻离开瞬时状态。
每个状态都具有某些可接受的输入,不同的输入会使状态机从当前状态转移到不同的新状 态。由于enum对其实例有严格限制,非常适合用来表现不同的状态和输入。一般而言,每个状态都具有一些相关的输出。
11.多路分发
Java只支持单路分发。也就是说,如果要执行的操作包含了不止一个类型未知的对象时,那么Java的动态绑定机制只能处理其中一个的类型。
要利用多路分发,程序员必须为每一个类型提供一个实际的方法调用,如果你要处理两个不同的类型体系,就需要为每个类型体系执行一个方法调用。一般而言,程序员需要有设定好的某种配置,以便一个方法调用能够引出更多的方法调用,从而能够在这个过程中处理多种类型。
要配置好多路分发需要很多的工序, 不过要记住,它的好处在于方法调用时的优雅的语法,这避免了在一个方法中判定多个对象的类型的丑陋代码, 你只需说,"嘿, 你们两个, 我不在乎你们是什么类型, 请你们自己交流 ”不过,在使用多路分发前, 请先明确,这种优雅的代码对你确实有重要的意义。
11.1 使用 enum分发
11.2 使用常量相关的方法
常量相关的方法允许我们为每个enum实例提供方法的不同实现, 这使得常量相关的方法似乎是实现多路分发的完美解决方案。不过, 通过这种方式, enum实例虽然可以具有不同的行为,但它们仍然不是类型, 不能将其作为方法签名中的参数类型来使用。最好的办法是将enum用在switch语句中。
11.3 使用EnumMap分发
使用EnumMap能够实现 “真正的“ 两路分发。 EnumMap是为enum专门设计的一种性能非常好的特殊Map。
11.4 使用二维数组
采用这种方式能够获得最简洁、 最直接的解决方案(很可能也是最快速的,虽然我们知道EnumMap内部其实也是使用数组实现的)。
不过,由于它使用的是数组,所以这种方式不太“安全”。如果使用一个大型数组,可能会不小心使用了错误的尺寸,而且,如果你的测试不能覆盖所有的可能性,有些错误可能会从你眼前溜过。
事实上,以上所有的解决方案只是各种不同类型的表罢了。不过,分析各种表的表现形式,
找出最适合的那一种,还是很有价值的。对于某类问题而言,”表驱动式编码”的概念具有非常强大的功能。
本章正好说明了一个“小”功能(enum)所能带来的价值。有时恰恰因为它,你才能够优雅而干净地解决问题。优雅与清晰很重要,正是它们区别了成功的解决方案与失败的解决方案。而失败的解决方案就是因为其他人无法理解它。