《Java编程思想》笔记19-枚举类型
基本 enum 特性
- 枚举型的实质是“单例模式”的变种,枚举型中的每个枚举都是它的一个静态成员字段。而且无法改变(常量)。
- Enum 类是枚举的一个封装类,是所有枚举类型的超类,它是一个没有抽象方法的抽象类。Enum 类实现了 Comparable 接口,所以它具有 compareTo() 方法。同时它还实现了 Serializable 接口。
- ordinal():获取枚举元素声明时的顺序,从 0 开始计算。
- 可以使用“==”来比较两个枚举实例相同与否,由于编译器自动实现了 equals() 和 hashCode() 两个方法。
- 调用
getDeclaringClass()
方法,我们就能知道其所属的 enum 类。 - name():返回 enum 实例声明时的名字,与使用 toString() 方法效果相同。
- 所有 enum 都继承了 Enum 类,所以 enum 不能再继承其他类,但是可以实现一个或多个接口。
向 enum 中添加新方法
除了不能继承自一个 enum 之外,可以将 enum 看作一个常规的类,也就是说可以向 enum 添加方法,甚至可以由 main 方法。
如果打算添加自己的方法,那么必须在 enum 实例的最后添加一个分号,而且必须先定义 enum 实例,实例之前不能有任何方法,否则报错。
只能在 enum 定义的内部使用其构造器创建 enum 实例,所以 enum 构造器声明为 private 并没有什么影响。
values() 的神秘之处
编译器自动合成了另外两个静态方法:values() 和 valueOf()。这两个是非常常用的用来获取枚举实例的手段:
- values(): 返回含有全部内部枚举实例的数组。
- valueOf(String name): 通过某个枚举实例的String字面量来获取与之对应的枚举实例。
如果将 enum 实例向上转型为 Enum,那么 values() 方法就不可访问了。不过,在 Class 中有一个 getEnumConstants()
方法,所以即便 Enum 接口中没有 values() 方法,我们仍然可以通过 Class 对象取得所有 enum 实例。
enum Search { HITHER, YON }
public class UpcastEnum {
public static void main(String[] args) {
Search[] vals = Search.values();
Enum e = Search.HITHER; // Upcast
// e.values(); // No values() in Enum
for(Enum en : e.getClass().getEnumConstants())
System.out.println(en);
}
} /* Output:
HITHER
YON
*///:~
使用接口组织枚举
我们希望从 enum 继承子类,是由于有时我们希望扩展远 enum 中的元素,有时是因为我们希望使用子类将一个 enum 中的元素进行分组。
在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。
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;
}
}
如果想创建一个“枚举的枚举”,那么可以创建一个新的 enum,然后用其实例包装 Food 中的每一个 enum 类。
public enum Course {
APPETIZER(Food.Appetizer.class),
MAINCOURSE(Food.MainCourse.class),
DESSERT(Food.Dessert.class),
COFFEE(Food.Coffee.class);
private Food[] values;
private Course(Class<? extends Food> kind) {
values = kind.getEnumConstants();
}
public Food randomSelection() {
return Enums.random(values);
}
}
另一种管理枚举的方法
public enum Meal2 {
APPETIZER(Food.Appetizer.class),
MAINCOURSE(Food.MainCourse.class),
DESSERT(Food.Dessert.class),
COFFEE(Food.Coffee.class);
private Food[] values;
private Meal2(Class<? extends Food> kind) {
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(Meal2 meal : Meal2.values()) {
Food food = meal.randomSelection();
System.out.println(food);
}
System.out.println("---");
}
}
}
EnumSet
函数名 | 说明 |
---|---|
allOf(Class<E> elementType) | 创建一个包含指定元素类型的所有元素的枚举 set。 |
complementOf(EnumSet<E> s) | 创建一个其元素类型与指定枚举 set 相同的枚举 set,最初包含指定 set 中所不 包含的此类型的所有元素。 |
copyOf(Collection<E> c) | 创建一个从指定 collection 初始化的枚举 set。 |
copyOf(EnumSet<E> s) | 创建一个其元素类型与指定枚举 set 相同的枚举 set,最初包含相同的元素(如果有的话)。 |
noneOf(Class<E> elementType) | 创建一个具有指定元素类型的空枚举 set。 |
of(E e) | 创建一个最初包含指定元素的枚举 set。重载函数可以接受多个元素 |
range(E from, E to) | 创建一个最初包含由两个指定端点所定义范围内的所有元素的枚举 set。 |
EnumMap
所有键都必须来自单个 enum。
“命令模式”,就是把“命令”当做 Key 值,然后相应的“行动”当做 Value 值,存储到 EnumMap 中。
enum AlarmPoint {
BATHROOM, UTILITY, KITCHEN
}
interface Command { void action(); }
public class EnumMaps {
public static void main(String[] args) {
EnumMap<AlarmPoint,Command> em = new EnumMap<AlarmPoint,Command>(AlarmPoint.class);
em.put(AlarmPoint.KITCHEN, new Command() {
public void action() { print("Kitchen fire!"); }
});
em.put(AlarmPoint.BATHROOM, new Command() {
public void action() { print("Bathroom alert!"); }
});
for(Map.Entry<AlarmPoint,Command> e : em.entrySet()) {
printnb(e.getKey() + ": ");
e.getValue().action();
}
}
} /* Output:
BATHROOM: Bathroom alert!
KITCHEN: Kitchen fire!
*///:~
每个枚举实例有自己特有的行为方法,是实际开发中非常常见的一种抽象。这种场景下,用 EnumMap 能使代码很清晰,简洁。
常量相关的方法
Java 允许 enum 实例编写方法,从而为每个 enum 实例赋予各自不同的行为。你需要为 enum 定义一个或多个 abstract 方法,然后为每个 enum 实例实现该抽象方法。
通过相应的 enum 实例,我们可以调用其上的方法。这通常也称为表驱动的代码,注意它与命令模式的区别。
enum LikeClasses {
WINKEN { void behavior() { print("Behavior1"); } },
BLINKEN { void behavior() { print("Behavior2"); } },
NOD { void behavior() { print("Behavior3"); } };
abstract void behavior();
}
职责链(Chain of Responsibility)是很常用的一种抽象。它的本质是把一系列的“操作”抽象成解决问题的一系列方法。在遇到问题之后,进行一一尝试,直到问题被解决为止,或者最终被标记为不可解决。
多路分发
问题
“多路分发(Multiple Dispatching)”模式源自于多对象交互的场景。比如下面这个算数的例子。
interface Number{
public Number plus(Number n);
public Number multiple(Number n);
}
class Integer extends Number{}
class Real extends Number{}
class Rational extends Number{}
Number 接口面向其他 Number 定义了加法和乘法。而 Number 底下有自然数,实数,有理数这样的派生类。所以当我们用两个数做加法或乘法的时候会像下面这样:
Number a=new Integer();
Number b=new Real();
a.plus(b);
但这里的问题是 Java 只支持单路分发,编译器只能对一个对象实施动态绑定。所以 a.plus(b);
是无法编译的。
解决方案
“石头剪刀布”游戏的例子
interface Item {
Outcome compete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}
class Paper implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return DRAW; }
public Outcome eval(Scissors s) { return WIN; }
public Outcome eval(Rock r) { return LOSE; }
}
class Scissors implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return LOSE; }
public Outcome eval(Scissors s) { return DRAW; }
public Outcome eval(Rock r) { return WIN; }
}
class Rock implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return WIN; }
public Outcome eval(Scissors s) { return LOSE; }
public Outcome eval(Rock r) { return DRAW; }
}
通过调用 Item.compete() 方法开始两路分发。当系统动态绑定 it 对象的运行时类型后,再通过 it 对象回调 this 对象。
用枚举型多路分发
用枚举实现多路分发,完全是另外一个思路:“写死”。下面是书里给的例子:
public enum RoShamBo2 implements Competitor<RoShamBo2> {
PAPER(DRAW, LOSE, WIN),
SCISSORS(WIN, DRAW, LOSE),
ROCK(LOSE, WIN, DRAW);
private Outcome vPAPER, vSCISSORS, vROCK;
RoShamBo2(Outcome paper,Outcome scissors,Outcome rock) {
this.vPAPER = paper;
this.vSCISSORS = scissors;
this.vROCK = rock;
}
public Outcome compete(RoShamBo2 it) {
switch(it) {
default:
case PAPER: return vPAPER;
case SCISSORS: return vSCISSORS;
case ROCK: return vROCK;
}
}
}
“原理”也很简单,用枚举把石头,剪刀,布之间的胜负关系全部写死。实际玩游戏的时候,对参数进行 switch。
使用常量相关的方法
public interface Competitor<T extends Competitor<T>> {
Outcome compete(T competitor);
}
public enum RoShamBo4 implements Competitor<RoShamBo4> {
ROCK {
public Outcome compete(RoShamBo4 opponent) {
return compete(SCISSORS, opponent);
}
},
SCISSORS {
public Outcome compete(RoShamBo4 opponent) {
return compete(PAPER, opponent);
}
},
PAPER {
public Outcome compete(RoShamBo4 opponent) {
return compete(ROCK, opponent);
}
};
Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {
return ((opponent == this) ? Outcome.DRAW
: ((opponent == loser) ? Outcome.WIN : Outcome.LOSE));
}
}
使用 EnumMap 分发
enum RoShamBo5 implements Competitor<RoShamBo5> {
PAPER, SCISSORS, ROCK;
static EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>> table =
new EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>>(RoShamBo5.class);
static {
for (RoShamBo5 it : RoShamBo5.values()){
table.put(it, new EnumMap<RoShamBo5, Outcome>(RoShamBo5.class));
}
initRow(PAPER, DRAW, LOSE, WIN);
initRow(SCISSORS, WIN, DRAW, LOSE);
initRow(ROCK, LOSE, WIN, DRAW);
}
static void initRow(RoShamBo5 it, Outcome vPAPER, Outcome vSCISSORS,Outcome vROCK) {
EnumMap<RoShamBo5, Outcome> row = RoShamBo5.table.get(it);
row.put(RoShamBo5.PAPER, vPAPER);
row.put(RoShamBo5.SCISSORS, vSCISSORS);
row.put(RoShamBo5.ROCK, vROCK);
}
public Outcome compete(RoShamBo5 it) {
return table.get(this).get(it);
}
}
使用二维数组
最简洁,最直接的方案。
enum RoShamBo6 implements Competitor<RoShamBo6> {
PAPER, SCISSORS, ROCK;
private static Outcome[][] table = {
{ DRAW, LOSE, WIN }, // PAPER
{ WIN, DRAW, LOSE }, // SCISSORS
{ LOSE, WIN, DRAW }, // ROCK
};
public Outcome compete(RoShamBo6 other) {
return table[this.ordinal()][other.ordinal()];
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《Java编程思想》笔记19-枚举类型
文章字数:2.3k
本文作者:Bin
发布时间:2018-06-23, 15:59:24
最后更新:2019-08-06, 00:46:42
原始链接:http://coolview.github.io/2018/06/23/Java%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3/%E3%80%8AJava%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3%E3%80%8B%E7%AC%94%E8%AE%B019-%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。