关键词enum可以将一组具名的值的有限集合创建为一种新的类型, 而这些具名的值可以作为常规的程序组件使用, 这是一种非常有用的功能
- 基本enum特性
enum Shrubbery {
GROUND, CRAWLING, HANGING
}
public class EnumClass {
public static void main(String[] args) throws Exception {
//values()方法遍历enum中所有的实例
for(Shrubbery s : Shrubbery.values()) {
//ordinal()返回实例在enum中唯一的序号(声明时的次序)
print(s + " oridinal: " + s.ordinal());
//enum实现了Comaparable接口, 所以具有compareTo接口
printnb(s.compareTo(Shrubbery.CRAWLING) + " ");
//enum自动提供了equals()方法和hashCode()方法
printnb(s.equals(Shrubbery.CRAWLING) + " ");
print(s == Shrubbery.CRAWLING);
//获取声明的enum名
print(s.getDeclaringClass());
print("------------");
}
}
}
导入enum
这是一个enum
public enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMING
}
静态导入这个enum
import static enumerated.Spiciness.*;
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree) {
this.degree = degree;
}
public String toString() {
return "Burrito is " + degree;
}
public static void main(String[] args) throws Exception {
System.out.println(new Burrito(NOT));
System.out.println(new Burrito(HOT));
}
}
- 向enum中添加新方法
可以向enum中添加方法, 甚至main方法
public enum OZWitch {
WEST("west"),
NORTH("north"),
EAST("east"),
SOUTH("south");
//必须在最后一个实例的最后加一个分号
private String description;
//添加了一个每一个实例用一个字符串描述自身的构造器
private OZWitch(String description) {
this.description = description;
}
public String getDescription() { return this.description; }
public static void main(String[] args) {
for(OZWitch witch : OZWitch.values()) {
print(witch + ": " + witch.getDescription());
}
}
}
覆盖enum的方法
public enum SpaceShip {
COUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP;
//覆盖toString()方法, 改变输出实例的格式
public String toString() {
//name()返回实例的名字, 即定义时全大写的形式
String id = name();
String lower = id.substring(1).toLowerCase();
//返回开头大写, 后面都是小写的形式
return id.charAt(0) + lower;
}
public static void main(String[] args) {
for(SpaceShip s : values()) {
System.out.println(s);
}
}
}
- 在switch中使用enum
enum Signal { GREEN, YELLOW, RED }
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch(color) {
case RED: color = Signal.GREEN;
break;
case GREEN: color = Signal.YELLOW;
break;
case YELLOW: color = Signal.RED;
}
}
}
enum中自动存储了每个实例的序号, 用switch读取enum实例的时候, 编译器会自动帮我们调用oridinal()获取它的序号值
- values()
enum Explore { HERE, THERE }
反编译后的Explore
Compiled from "Reflection.java"
final class enumerated.Explore extends java.lang.Enum<enumerated.Explore> {
public static final enumerated.Explore HERE;
public static final enumerated.Explore THERE;
static {};
public static enumerated.Explore[] values();
public static enumerated.Explore valueOf(java.lang.String);
}
values()方法是编译器向enum中添加的static方法, 如果你把enum实例向上转型成Enum, 就失去这个方法了
但是在Enum的Class里有一个getEnumConstants()方法, 可以获取实例
for(Enum en : e.getClass().getEnumConstants())
System.out.println(en);
- 实现, 而非继承
enum都继承自Enum, 所以不能再继承其他类, 只能实现接口
随机选取
下面是一个生成随机选取实例的例子
public class Enums {
private static Random rand = new Random(47);
public static <T extends Enum<T>> T random(Class<T> ec) {
return random(ec.getEnumConstants());
}
public static <T> T random(T[] values) {
return values[rand.nextInt(values.length)];
}
}
<T extends Enum<T>>表示T是Enum的一个实例, 将Class<T>作为参数, 就可以获取实例的数组
重载的从实例数组随机选取的方法就跟Enum类无关了
- 使用接口组织枚举
在一个接口的内部, 创建实现该接口的枚举, 以此将枚举分组
下面这些枚举代表不同的食物种类, 它们都实现了food接口, 可以向上转型为统一的food类型
public interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOD
}
}
可以像下面这样, 写一个枚举的枚举, 由于这些枚举都是实现了同一接口的子枚举, 可以将它们向上转型
为食物类型
public enum Course {
APPETIZER(Food.Appetizer.class),
MAINCOURSE(Food.MainCourse.class);
private Food[] values;
private Course(Class<? extends Food> kind) {
values = kind.getEnumConstants();
}
public Food randomSelection() {
return Enums.random(values);
}
}
取出每个枚举以及它包含的实例的时候, 可以转换为food类型
for(int i = 0;i < 5;i++) {
for(Course course : Course.values()) {
Food food = course.randomSelection();
System.out.println(food);
}
System.out.println("---");
}
或者直接把接口嵌套在一个枚举里面
public enum SecurityCategory {
//STOCK和BOND里面存储的是接口里面定义的两个枚举的实例
STOCK(Security.Stock.class),
BOND(Security.Bond.class);
private Security[] values;
SecurityCategory(Class<? extends Security> kind) {
values = kind.getEnumConstants();
}
interface Security {
enum Stock implements Security { SHORT, LONG, MARGIN }
enum Bond implements Security { MUNICIPAL, JUNK }
}
public Security randomSelection() {
return Enums.random(values);
}
public static void main(String[] args) {
for(int i = 0;i < 10;i++) {
SecurityCategory category =
Enums.random(SecurityCategory.class);
System.out.println(category.randomSelection());
}
}
}
- 使用EnumSet替代标志
将一种enum类型的一些实例放进EnumSet中
public class EnumSets {
public static void main(String[] args) throws Exception {
//noneof()创建一个空的EnumSet
//初始化用enum.class
EnumSet<AlarmPoints> points =
EnumSet.noneOf(AlarmPoints.class);
points.add(BATHROOM);
print(points);
points.addAll(EnumSet.of(START1, START2, KITCHEN));
print(points);
//加入整个enum
points = EnumSet.allOf(AlarmPoints.class);
print(points);
points.removeAll(EnumSet.of(START1, START2, KITCHEN));
print(points);
points = EnumSet.complementOf(points);
}
}
- EnumMap
EnumMap要求其中的键必须来自一个enum,
由于enum数量的限制, 所以EnumMap内部可以由数组实现
Command用了命令模式, 是一个只有一个方法的接口
interface Command { void action(); }
public class EnumMaps {
public static void main(String[] args) throws Exception {
//初始化也是用enum.class
EnumMap<AlarmPoints, Command> em =
new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
em.put(KITCHEN, new Command() {
public void action() {
print("kitchen fire!");
}
});
em.put(BATHROOM, new Command() {
public void action() {
print("Bathroom alert!");
}
});
for(Map.Entry<AlarmPoints, Command> e : em.entrySet()) {
printnb(e.getKey() + ": ");
e.getValue().action();
}
try {
em.get(UTILITY);
} catch(Exception e) {
print(e);
}
}
}
每一个enum的实例可以在内部定义一些自己的行为,
在enum中定义一个抽象方法, 然后为每个实例实现这个方法
public enum ConstantSpecificMethod {
DATE_TIME {
String getInfo() {
return DateFormat.getDateInstance().format(new Date());
}
},
CLASSPATH {
String getInfo() {
return System.getProperty("classpath");
}
},
VERSION {
String getInfo() {
return System.getProperty("java.version");
}
};
abstract String getInfo();
public static void main(String[] args) {
for(ConstantSpecificMethod csm : values())
System.out.println(csm.getInfo());
}
}
看上去实例就像是enum的子类一样, 用这种方式实现了多态, 实际上并不是
enum的实例只是enum的static final对象, 它不能访问enum中的非静态成员, 所实现的抽象方法所能实现的方法也是有限的,
这些方法只能是常量相关的
下面是一个enum反编译后的结果
abstract class enumerated.LikeClasses extends java.lang.Enum<enumerated.LikeClasses> {
public static final enumerated.LikeClasses WINKEN;
public static final enumerated.LikeClasses BLINKEN;
public static final enumerated.LikeClasses NOD;
static {};
abstract void behavior();
public static enumerated.LikeClasses[] values();
public static enumerated.LikeClasses valueOf(java.lang.String);
enumerated.LikeClasses(java.lang.String, int, enumerated.LikeClasses);
}
枚举中实例的方法, 也可以覆盖掉enum中已有的方法
使用enum职责链
职责链设计模式是为了解决一个问题, 把不同的解决方案放到一起形成一个职责链, 当有问题的请求来时, 逐一执行链上每一环, 直
到解决问题
下面是一个邮局处理信件的例子
//每一个Mail对象代表一封待解决的邮件
class Mail {
//这每一个enum代表一种对信件处理的方式, 把它们串起来就是一个职责链
//每一次尝试都可以看作一个策略, 只有当策略返回YES, OK表示解决时, 完成职责链
enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}
enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}
enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}
enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}
enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}
GeneralDelivery generalDelivery;
Scannability scannability;
Readability readability;
Address address;
ReturnAddress returnAddress;
static long counter = 0;
long id = counter++;
public String toString() { return "Main " + id; }
public String details() {
return toString() +
", General Delivery: " + generalDelivery +
", Address Scannability: " + scannability +
", Address Readability: " + readability +
", Address Address: " + address +
", Return address: " + returnAddress;
}
//随机产生一个Mail对象, 里面为每一种enum随机分配一个实例
public static Mail randomMail() {
Mail m = new Mail();
m.generalDelivery = Enums.random(GeneralDelivery.class);
m.scannability = Enums.random(Scannability.class);
m.readability = Enums.random(Readability.class);
m.address = Enums.random(Address.class);
m.returnAddress = Enums.random(ReturnAddress.class);
return m;
}
//Mail的迭代器, 每次迭代产生是上面的随机邮件
public static Iterable<Mail> generator(final int count) {
return new Iterable<Mail>() {
int n = count;
public Iterator<Mail> iterator() {
return new Iterator<Mail>() {
public boolean hasNext() { return n-- > 0; }
public Mail next() { return randomMail(); }
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}
public class PostOffice {
//用MailHandler进行每个实例的尝试, 当有一种实例返回成功的结果, 就返回true, 表示邮件被成功解决
enum MailHandler {
GENERAL_DELIVERY {
boolean handle(Mail m) {
switch(m.generalDelivery) {
case YES:
print("Using general delivery for " + m);
return true;
default:
return false;
}
}
},
MACHINE_SCAN {
boolean handle(Mail m) {
switch(m.scannability) {
case UNSCANNABLE: return false;
default: {
switch (m.address) {
case INCORRECT: return false;
default:
print("Delivering " + m + " automatically");
return true;
}
}
}
}
},
VISUAL_INSPECTION {
boolean handle(Mail m) {
switch(m.readability) {
case ILLEGIBLE: return false;
default: {
switch (m.address) {
case INCORRECT: return false;
default:
print("Delivering " + m + " normally");
return true;
}
}
}
}
},
RETURN_TO_SENDER {
boolean handle(Mail m) {
switch(m.returnAddress) {
case MISSING: return false;
default: {
switch (m.address) {
case INCORRECT: return false;
default:
print("Returning " + m + " to sender");
return true;
}
}
}
}
};
abstract boolean handle(Mail m);
}
//遍历enum MailHandler的每种尝试, 里面可以调用不同的handle方法
static void handle(Mail m) {
for(MailHandler handler : MailHandler.values())
if(handler.handle(m))
return;
print(m + " is a dead letter");
}
//随机产生10个Mail, 对每个Mail, 执行一次职责链
public static void main(String[] args) throws Exception {
for(Mail mail : Mail.generator(10)) {
print(mail.details());
handle(mail);
print("********");
}
}
}
使用enum的状态机
下面用了一个自动售货机的例子来表示一个状态机
定义个一个输入enum, Input每个实例具有一个值, 代表钱的价值或者商品价值
//用一个enum将这些输入状态归类
enum Category {
//不同种类的钱
MONEY(NICKEL, DIME, QUARTER, DOLLAR),
//不同种类的商品
ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP),
//结束交易
QUIT_TRANSACTION(ABORT_TRANSACTION),
//结束整个状态
SHUT_DOWN(STOP);
//每个分类实例中放置的Input实例
private Input[] values;
//多个参数来初始化分类实例
Category(Input... types) { values = types; }
//分类eumMap, key表示一个Input, value表示它所属的分类实例
private static EnumMap<Input, Category> categories =
new EnumMap<Input, Category>(Input.class);
static {
//每个分类实例下的Input实例数组, 作为key, 分类实例作为value, 构建map
for(Category c : Category.class.getEnumConstants())
for(Input type : c.values)
categories.put(type,c);
}
//根据Input实例, 返回所属的分类
public static Category categorize(Input input) {
return categories.get(input);
}
}
//上面这个分类就是得到一个根据输入的实例, 得到输入类型分类的功能
//下面这个就是自动售货机的状态机
public class VendingMachine {
//State enum, 表示当前售货机的状态, RESTING为原始状态
private static State state = State.RESTING;
//表示售货机中收到的钱数
private static int amount = 0;
//表示选择的商品
private static Input selection = null;
enum StateDuration { TRANSIENT; }
//State状态enum, 为每一状态定义next()方法, 确定下一个状态, 以及售贩等操作
enum State {
//原始状态
RESTING {
void next(Input input) {
//根据输入所属类型来进行不同操作
switch(Category.categorize(input)) {
//钱, 客人给的钱, 把数额加到售货机中收到的钱中, 转为继续收钱状态
case MONEY:
amount += input.amount();
state = ADDING_MONEY;
break;
//退出售货机, 转为结束状态
case SHUT_DOWN:
state = TERMINAL;
default:
}
}
},
//继续收钱状态
ADDING_MONEY {
void next(Input input) {
switch(Category.categorize(input)) {
//继续收钱
case MONEY:
amount += input.amount();
break;
//选择商品, 如果收的钱不够商品价格, 输出钱不够, 否则转换为商品结算状态
case ITEM_SELECTION:
selection = input;
if(amount < selection.amount())
print("Insufficient money for " + selection);
else state = DISPENSING;
break;
//放弃交易, 进入找钱状态
case QUIT_TRANSACTION:
state = GIVING_CHANGE;
break;
//退出售货机, 转为结束状态
case SHUT_DOWN:
state = TERMINAL;
default:
}
}
},
//结算商品状态
DISPENSING(StateDuration.TRANSIENT) {
//从收到的钱中扣除商品价格, 进入找钱状态
void next() {
print("here is your " + selection);
amount -= selection.amount();
state = GIVING_CHANGE;
}
},
//找钱状态
GIVING_CHANGE(StateDuration.TRANSIENT) {
void next() {
//收到的钱还有剩, 找钱, 并清空收到的钱, 转换为初始状态
if(amount > 0) {
print("Your change: " + amount);
amount = 0;
}
state = RESTING;
}
},
//结束状态
TERMINAL {
void output() { print("Halted"); };
};
//每个状态实例是否是一个暂时性状态, 如果是暂时性状态, 处理完状态后, 不需要输入, 直接跳转到下一个状态
private boolean isTransient = false;
State() {}
//暂时性状态初始化
State(StateDuration trans) {
isTransient = true;
}
//非暂时性状态, 状态改变需要输入
void next(Input input) {
throw new RuntimeException();
}
//暂时性状态, 状态改变不需要输入
void next() {
throw new RuntimeException();
}
//输出售货机中收到的钱
void output() { print("amount: "+amount); }
}
//根据输入自动运行售货机
static void run(Generator<Input> gen) {
//遇到结束状态, 推出
while(state != State.TERMINAL) {
Input in = gen.next();
System.out.println("Input: " + in);
//获取输入并根据输入执行当前状态的方法, 调转至下一状态
state.next(in);
//从下一状态开始连续的暂时性状态, 直接执行转换下去, 直到遇到非暂时性状态
while(state.isTransient)
state.next();
//输出钱数
state.output();
}
}
public static void main(String[] args) throws Exception {
//从文件中按";"分隔, 读取一组Input实例, 然后运行这些实例, 查看售货机的运行状态
Generator<Input> gen = new RandomInputGenerator();
gen = new FileInputGenerator("VendingMachingInput.txt");
run(gen);
}
}
class RandomInputGenerator implements Generator<Input> {
public Input next() { return Input.randomSelection(); }
}
class FileInputGenerator implements Generator<Input> {
private Iterator<String> input;
public FileInputGenerator(String fileName) {
input = new TextFile(fileName,";").iterator();
}
public Input next() {
if(!input.hasNext())
return null;
return Enum.valueOf(Input.class, input.next().trim());
}
}
/*
VendingMachingInput.txt
QUARTER; QUARTER; QUARTER; CHIPS;
DOLLAR; DOLLAR; TOOTHPASTE;
QUARTER; DIME; ABORT_TRANSACTION;
QUARTER; DIME; SODA;
QUARTER; DIME; NICKEL; SODA;
ABORT_TRANSACTION;
STOP;
*/
- 多路分发
执行的操作包含了不止一个类型未知对象的操作, 要使用多路分发
两路分发, 必须要有两个方法调用, 第一个方法调用决定第一个未知类型, 第二个调用决定第二个未知类型
下面是一个石头剪刀布的两路分发
public enum Outcome {
WIN, LOSE, DRAW
}
//Item是这几种类型的接口, 被用作多路分发
interface Item {
Outcome compete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}
class Paper implements Item {
//第一个方法compete()决定第一个类型, 完成第一次分发, 即发起比较的类型, 其中的参数Item是第二个类型,
//把发起比较的类型当作第二个类型的参数this, 放在第二个方法中
public Outcome compete(Item it) { return it.eval(this); }
//第二个方法eval()决定第二个类型, 完成第二次分发, 表示被比较的类型, 它的参数是在第一次分发中确定的
//发起比较的类型, 那么可以根据具体的参数类型重载这个方法返回比较结果
public Outcome eval(Paper p) { return DRAW; }
public Outcome eval(Scissors s) { return WIN; }
public Outcome eval(Rock r) { return LOSE; }
public String toString() { return "Paper"; }
}
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; }
public String toString() { return "Scissors"; }
}
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; }
public String toString() { return "Rock"; }
}
public class RoShambo1 {
static final int SIZE = 20;
private static Random rand = new Random(47);
public static Item newItem() {
switch(rand.nextInt(3)) {
default:
case 0: return new Scissors();
case 1: return new Paper();
case 2: return new Rock();
}
}
public static void match(Item a, Item b) {
System.out.println(a + " vs. " + b + ": " + a.compete(b));
}
public static void main(String[] args) throws Exception {
for(int i = 0;i < SIZE;i++)
match(newItem(), newItem());
}
}
这个例子没有用到enum, 而且enum是不能作为真正的类型的
下面是使用enum进行多路分发的方法
RoShamBo2.java
//这个enum的每个实例代表一种类型, 石头, 剪刀, 或布, 它们每个都存储了, 对石头, 对剪刀, 对布的胜负结果
public enum RoShamBo2 implements Competitor<RoShamBo2>{
PAPER(DRAW, LOSE, WIN),
SCISSORS(WIN, DRAW, LOSE),
ROCK(LOSE, WIN, DRAW);
//对剪刀, 对石头, 对布的结果
private Outcome vPaper, vScissors, vRock;
private RoShamBo2(Outcome paper, Outcome scissors, Outcome rock) {
this.vPaper = paper;
this.vScissors = scissors;
this.vRock = rock;
}
//去和另一个RoShamBo2实例比较, 根据参数是哪个实例, 返回this对这个实例类型的结果
public Outcome compete(RoShamBo2 it) {
switch(it) {
default:
case PAPER: return vPaper;
case SCISSORS: return vScissors;
case ROCK: return vRock;
}
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo2.class, 20);
}
}
RoShamBo.java
public class RoShamBo {
public static <T extends Competitor<T>>
void match(T a, T b) {
System.out.println(a + " vs. " + b + ": " + a.compete(b));
}
public static <T extends Enum<T> & Competitor<T>>
void play(Class<T> rsbClass, int size) {
for(int i = 0;i < size;i++)
match(Enums.random(rsbClass),Enums.random(rsbClass));
}
}
Competitor.java
//这个泛型只能存那个类本身
public interface Competitor<T extends Competitor<T>> {
Outcome compete(T competitor);
}
下面是一个使用EnumMap实现多路分发的例子
public enum RoShamBo5 implements Competitor<RoShamBo5> {
PAPER, SCISSORS, ROCK;
//这里一个EnumMap, key存发起比较的实例, value是一个EnumMap, 它的key是被比较的实例, value是比较结果
//它是一个static成员, 已经预先存储好所有类型的所有行为以及产生的所有结果
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 =
table.get(it);
row.put(RoShamBo5.PAPER,vPaper);
row.put(RoShamBo5.SCISSORS,vScissors);
row.put(RoShamBo5.ROCK,vRock);
}
//根据EnumMap的结果, 当前实例this作为EnumMap的第一维key被get后, 得到该类型所对应的行为信息, 完成第一次分发
//第二次get被比较的实例it, 得到第二次分发后的信息
public Outcome compete(RoShamBo5 it) {
return table.get(this).get(it);
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo5.class, 20);
}
}