方法添加

除了不能继承自一个 enum 之外,我们基本上可以将 enum 看作一个常规的类。也就是说我们可以向 enum 中添加方法。enum 甚至可以有 main() 方法。

values 方法的神秘之处

前面已经提到,编译器为你创建的 enum 类都继承自 Enum 类。然而,如果你研究一下 Enum 类就会发现,它并没有 values() 方法—-答案是,values() 是由编译器添加的 static 方法。

使用接口组织枚举

无法从 enum 继承子类有时很令人沮丧。这种需求有时源自我们希望扩展原 enum 中的元素,有时是因为我们希望使用子类将一个 enum 中的元素进行分组。

  1. // enums/menu/Food.java
  2. // Subcategorization of enums within interfaces
  3. package enums.menu;
  4. public interface Food {
  5. enum Appetizer implements Food {
  6. SALAD, SOUP, SPRING_ROLLS;
  7. }
  8. enum MainCourse implements Food {
  9. LASAGNE, BURRITO, PAD_THAI,
  10. LENTILS, HUMMOUS, VINDALOO;
  11. }
  12. enum Dessert implements Food {
  13. TIRAMISU, GELATO, BLACK_FOREST_CAKE,
  14. FRUIT, CREME_CARAMEL;
  15. }
  16. enum Coffee implements Food {
  17. BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
  18. LATTE, CAPPUCCINO, TEA, HERB_TEA;
  19. }
  20. }

EnumSet

Set 是一种集合,只能向其中添加不重复的对象。当然,enum 也要求其成员都是唯一的,所以 enum 看起来也具有集合的行为。不过,由于不能从 enum 中删除或添加元素,所以它只能算是不太有用的集合。Java SE5 引入 EnumSet,是为了通过 enum 创建一种替代品,以替代传统的基于 int 的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些 bit,而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。

EnumSet 的设计充分考虑到了速度因素,因为它必须与非常高效的 bit 标志相竞争(其操作与 HashSet 相比,非常地快)

使用 EnumMap

EnumMap 是一种特殊的 Map,它要求其中的键(key)必须来自一个 enum,由于 enum 本身的限制,所以 EnumMap 在内部可由数组实现。因此 EnumMap 的速度很快,我们可以放心地使用 enum 实例在 EnumMap 中进行查找操作。不过,我们只能将 enum 的实例作为键来调用 put() 可方法,其他操作与使用一般的 Map 差不多。

常量特定方法

Java 的 enum 有一个非常有趣的特性,即它允许程序员为 enum 实例编写方法,从而为每个 enum 实例赋予各自不同的行为。要实现常量相关的方法,你需要为 enum 定义一个或多个 abstract 方法,然后为每个 enum 实例实现该抽象方法。

  1. // enums/ConstantSpecificMethod.java
  2. import java.util.*;
  3. import java.text.*;
  4. public enum ConstantSpecificMethod {
  5. DATE_TIME {
  6. @Override
  7. String getInfo() {
  8. return
  9. DateFormat.getDateInstance()
  10. .format(new Date());
  11. }
  12. },
  13. CLASSPATH {
  14. @Override
  15. String getInfo() {
  16. return System.getenv("CLASSPATH");
  17. }
  18. },
  19. VERSION {
  20. @Override
  21. String getInfo() {
  22. return System.getProperty("java.version");
  23. }
  24. };
  25. abstract String getInfo();
  26. public static void main(String[] args) {
  27. for(ConstantSpecificMethod csm : values())
  28. System.out.println(csm.getInfo());
  29. }
  30. }
  31. 输出为:
  32. May 9, 2017
  33. C:\Users\Bruce\Documents\GitHub\on-
  34. java\ExtractedExamples\\gradle\wrapper\gradle-
  35. wrapper.jar
  36. 1.8.0_112

通过相应的 enum 实例,我们可以调用其上的方法。这通常也称为表驱动的代码
然而,enum 实例与类的相似之处也仅限于此了。我们并不能真的将 enum 实例作为一个类型来使用

除了实现 abstract 方法以外,程序员是否可以覆盖常量相关的方法呢?答案是肯定的,参考下面的程序:

  1. // enums/OverrideConstantSpecific.java
  2. public enum OverrideConstantSpecific {
  3. NUT, BOLT,
  4. WASHER {
  5. @Override
  6. void f() {
  7. System.out.println("Overridden method");
  8. }
  9. };
  10. void f() {
  11. System.out.println("default behavior");
  12. }
  13. public static void main(String[] args) {
  14. for(OverrideConstantSpecific ocs : values()) {
  15. System.out.print(ocs + ": ");
  16. ocs.f();
  17. }
  18. }
  19. }
  20. 输出为:
  21. NUT: default behavior
  22. BOLT: default behavior
  23. WASHER: Overridden method

使用 enum 的职责链

在职责链(Chain of Responsibility)设计模式中,程序员以多种不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。

多路分发

当你要处理多种交互类型时,程序可能会变得相当杂乱。当你声明 a.plus(b) 时,你并不知道 a 或 b 的确切类型,那你如何能让它们正确地交互呢?

java只支持单路分发,也就是说如果要执行的操作包含不止一个位置对象类型时,那么java动态绑定机制只能处理其中一个的类型的方法, 所以针对多个未知类型的对象时,你就必须自己去判断对象的类型
ps:动态绑定参考 章节-多态 跟章节-类型

解决上面问题的办法就是多路分发(在那个例子中,只有两个分发,一般称之为两路分发).多态只能发生在方法调用时,所以,如果你想使用两路分发,那么就必须有两个方法调用:第一个方法调用决定第一个未知类型,第二个方法调用决定第二个未知的类型。

  1. // enums/Outcome.java
  2. package enums;
  3. public enum Outcome { WIN, LOSE, DRAW }
  4. // enums/RoShamBo1.java
  5. // Demonstration of multiple dispatching
  6. // {java enums.RoShamBo1}
  7. package enums;
  8. import java.util.*;
  9. import static enums.Outcome.*;
  10. interface Item {
  11. Outcome compete(Item it);
  12. Outcome eval(Paper p);
  13. Outcome eval(Scissors s);
  14. Outcome eval(Rock r);
  15. }
  16. class Paper implements Item {
  17. @Override
  18. public Outcome compete(Item it) {
  19. return it.eval(this);
  20. }
  21. @Override
  22. public Outcome eval(Paper p) { return DRAW; }
  23. @Override
  24. public Outcome eval(Scissors s) { return WIN; }
  25. @Override
  26. public Outcome eval(Rock r) { return LOSE; }
  27. @Override
  28. public String toString() { return "Paper"; }
  29. }
  30. class Scissors implements Item {
  31. @Override
  32. public Outcome compete(Item it) {
  33. return it.eval(this);
  34. }
  35. @Override
  36. public Outcome eval(Paper p) { return LOSE; }
  37. @Override
  38. public Outcome eval(Scissors s) { return DRAW; }
  39. @Override
  40. public Outcome eval(Rock r) { return WIN; }
  41. @Override
  42. public String toString() { return "Scissors"; }
  43. }
  44. class Rock implements Item {
  45. @Override
  46. public Outcome compete(Item it) {
  47. return it.eval(this);
  48. }
  49. @Override
  50. public Outcome eval(Paper p) { return WIN; }
  51. @Override
  52. public Outcome eval(Scissors s) { return LOSE; }
  53. @Override
  54. public Outcome eval(Rock r) { return DRAW; }
  55. @Override
  56. public String toString() { return "Rock"; }
  57. }
  58. public class RoShamBo1 {
  59. static final int SIZE = 20;
  60. private static Random rand = new Random(47);
  61. public static Item newItem() {
  62. switch(rand.nextInt(3)) {
  63. default:
  64. case 0: return new Scissors();
  65. case 1: return new Paper();
  66. case 2: return new Rock();
  67. }
  68. }
  69. public static void match(Item a, Item b) {
  70. System.out.println(
  71. a + " vs. " + b + ": " + a.compete(b));
  72. }
  73. public static void main(String[] args) {
  74. for(int i = 0; i < SIZE; i++)
  75. match(newItem(), newItem());
  76. }
  77. }
  78. 输出为:
  79. Rock vs. Rock: DRAW
  80. Paper vs. Rock: WIN
  81. Paper vs. Rock: WIN
  82. Paper vs. Rock: WIN
  83. Scissors vs. Paper: WIN
  84. Scissors vs. Scissors: DRAW
  85. Scissors vs. Paper: WIN
  86. Rock vs. Paper: LOSE
  87. Paper vs. Paper: DRAW
  88. Rock vs. Paper: LOSE
  89. Paper vs. Scissors: LOSE
  90. Paper vs. Scissors: LOSE
  91. Rock vs. Scissors: WIN
  92. Rock vs. Paper: LOSE
  93. Paper vs. Rock: WIN
  94. Scissors vs. Paper: WIN
  95. Paper vs. Scissors: LOSE
  96. Paper vs. Scissors: LOSE
  97. Paper vs. Scissors: LOSE
  98. Paper vs. Scissors: LOSE

Item 是这几种类型的接口,将会被用作多路分发。RoShamBo1.match() 有两个 Item 参数,通过调用 Item.compete() 方法开始两路分发,分发机制会在 a 的实际类型的 compete(内部起到分发的作用。compete() 方法通过调用 eval() 来为另一个类型实现第二次分法。也就是说 a.compete(b)的判断类型的过程是a.compete判断了a的类型 ,然后用this保留,然后调用b的eval,这样又拿到了b