JDK5.0开始引进了java Enum枚举类型,它可作为我们在编写代码时的一个技巧,有时恰恰因为它,我们能够"优雅而干净"地解决问题。在使用枚举类型时,我们所编写的枚举类都是隐式地继承于java.lang.Enum类,枚举类的每个成员都被隐式声明为public static final,可以通过类名称直接使用它们。
由于枚举类型本质上就是一个类,所以可以在一个独立的文件中来声明枚举值,或者在某个类中声明枚举成员。要定义枚举类型是使用enum关键词。下面就枚举的用法作一个小结:
用法一:提供常量、可用于Switch中
这是它的最基本的用法,把相关的常量定义到一个枚举类型里。
用法二:覆盖枚举的方法
我们可以覆盖枚举的方法,比如toString()方法取得枚举值的字符串描述;values()方法取得所有的枚举成员实例,并以数组方式返回;静态valueOf()方法可以让将指定的字符串尝试转换为枚举类型;使用compareTo()方法比较两个枚举值在枚举时的顺序;使用ordinal()方法得到每个枚举成员在依枚举顺序中的位置索引,默认以0开始。
用法三:构造函数
由于枚举是继承自java.lang.Enum的类,所以也可以定义构造函数,但所定义的函数不能是Public的构造函数。这是为了避免直接实例化枚举类型。注:枚举类中的静态valueOf()方法可以让将指定的字符串尝试转换为枚举类型,如下:
public class EnumClassTest {
enum OrderStatusEnum {
CREATE, PENDING, PICK, PACK, SHIPPING, COMPLETED,
};
public static void main(String[] args) {
for (String s : "CREATE ORDER".split(" ")) {
System.err.println("String is " + s);
if(s.compareTo(OrderStatusEnum.CREATE.toString()) == 0) {
OrderStatusEnum status = Enum.valueOf(OrderStatusEnum.class, s);
System.err.println("s1 == " + status.name()+", value = "+ status.ordinal() + ", " + status.toString());
System.err.println("Order is " + status);
}
}
}
}
运行结果如下:
String is CREATE
s1 == CREATE, value = 0, CREATE
Order is CREATE
String is ORDER
用法四:定义新方法,为每个enum实例赋予各自不同的行为
为每一个枚举实例定义不同的行为,我们可以定义一个或者多个抽象方法作为枚举的一部分,然后为每个枚举实例定义方法。如下:
public enum ConstantSpecificMethod {
DATE_TIME {String getInfo() {
return DateFormat.getDateInstance().format(new Date());
}},
CLASSPATH {String getInfo() {
return System.getenv("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来定义一个简单的状态机应用。
用法五:实现接口定义
由于枚举是继承自java.lang.Enum类,由于Java不支持多继承,所以枚举只能实现接口页不能再继承其他类。
当我们希望扩展枚举的数量或希望对枚举进行分组时,这时就可以通过在接口内定义分组的枚举,然后通过继承这个接口来使用枚举。如下:
enum SecurityCategory {
STOCK(Security.Stock.class), BOND(Security.Bond.class);
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 + ": " +
category.randomSelection());
}
}
}
用法六:遍历
枚举中的values方法可以获得按枚举定义顺序生成的数组,通过获得枚举中内置的整形顺序,枚举因而被历遍。如下:
public class EnumClassTest {
enum OrderStatusEnum {
CREATE, PENDING, PICK, PACK, SHIPPING, COMPLETED,
};
public static void main(String[] args) {
for (OrderStatusEnum s : OrderStatusEnum.values()) {
System.err.println(s + " ordinal: " + s.ordinal());
System.err.println(s.compareTo(OrderStatusEnum.CREATE) + " ");
System.err.println(s.equals(OrderStatusEnum.CREATE) + " ");
System.err.println(s == OrderStatusEnum.CREATE);
System.err.println(s.getDeclaringClass());
System.err.println(s.name());
System.err.println("----------------------");
}
}
}
运行结果如下:
CREATE ordinal: 0
0
true
true
class org.jevo.enusample.EnumClassTest$OrderStatusEnum
CREATE
----------------------
PENDING ordinal: 1
1
false
false
class org.jevo.enusample.EnumClassTest$OrderStatusEnum
PENDING
----------------------
PICK ordinal: 2
2
false
false
class org.jevo.enusample.EnumClassTest$OrderStatusEnum
PICK
----------------------
PACK ordinal: 3
3
false
false
class org.jevo.enusample.EnumClassTest$OrderStatusEnum
PACK
----------------------
SHIPPING ordinal: 4
4
false
false
class org.jevo.enusample.EnumClassTest$OrderStatusEnum
SHIPPING
----------------------
COMPLETED ordinal: 5
5
false
false
class org.jevo.enusample.EnumClassTest$OrderStatusEnum
COMPLETED
----------------------
枚举类型的values()方法是编译器插入到enum定义中的static方法,当将enum实例转换成父类Enum类型时,是不能访问values()方法的。但是通过Class的getEnumConstants()方法,仍然可以取得所有的enum实例。如下:
public class EnumClassTest {
enum OrderStatusEnum {
CREATE, PENDING, PICK, PACK, SHIPPING, COMPLETED,
};
public static void main(String[] args) {
for (OrderStatusEnum s : OrderStatusEnum.values()) {
OrderStatusEnum[] enus = s.getClass().getEnumConstants();
for (OrderStatusEnum s1 : enus) {
System.err.println("enus == " + s1.name()+"");
}
System.err.println("----------------------");
}
}
}
运行结果如下:
enus == CREATE
enus == PENDING
enus == PICK
enus == PACK
enus == SHIPPING
enus == COMPLETED
----------------------
enus == CREATE
enus == PENDING
enus == PICK
enus == PACK
enus == SHIPPING
enus == COMPLETED
----------------------
enus == CREATE
enus == PENDING
enus == PICK
enus == PACK
enus == SHIPPING
enus == COMPLETED
----------------------
enus == CREATE
enus == PENDING
enus == PICK
enus == PACK
enus == SHIPPING
enus == COMPLETED
----------------------
enus == CREATE
enus == PENDING
enus == PICK
enus == PACK
enus == SHIPPING
enus == COMPLETED
----------------------
enus == CREATE
enus == PENDING
enus == PICK
enus == PACK
enus == SHIPPING
enus == COMPLETED
----------------------
用法七:枚举集合的使用
两个枚举集合是 java.util.EnumSet和java.util.EnumMap。EnumSet保证集合中的元素不重复;EnumMap中的key是enum类型,而value则可以是任意类型。但是enum中不能删除添加元素。
Java SE5中EnumSet和EnumMap用于替换基于整型的位标志。位标志通常用来指示某种信息的开关量,但是在代码中是对位进行操作并不是一个有语义的概念,因而不容易被理解。EnumSet的效率比位标志要快,它在内部使用了long来表示一个位向量,然后就可以使用更加概念化的语言来表示某个位的开关,而不用担心效率。注意的是EnumSet中的元素必须来自同一个枚举。如下:
public class EnumSets {
enum AlarmPoints {STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,OFFICE4, BATHROOM, UTILITY, KITCHEN};
public static void main(String[] args) {
EnumSet points1 = EnumSet.allOf(AlarmPoints.class); //所有元素的枚举set
System.err.println(points1);
EnumSet points = EnumSet.noneOf(AlarmPoints.class); //空Set
points.add(AlarmPoints.BATHROOM);
System.err.println(points);
points.addAll(EnumSet.of(AlarmPoints.STAIR1, AlarmPoints.STAIR2, AlarmPoints.KITCHEN)); //包含指定元素的枚举
System.err.println(points);
points = EnumSet.allOf(AlarmPoints.class);
points.removeAll(EnumSet.of(AlarmPoints.STAIR1, AlarmPoints.STAIR2, AlarmPoints.KITCHEN));
System.err.println(points);
points.removeAll(EnumSet.range(AlarmPoints.OFFICE1, AlarmPoints.OFFICE4));//包含两端范围内的所有元素的枚举set
System.err.println(points);
points = EnumSet.complementOf(points); //包含指定 set 中不 包含所有元素
System.err.println(points);
}
}
运行结果如下:
[STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY, KITCHEN]
[BATHROOM]
[STAIR1, STAIR2, BATHROOM, KITCHEN]
[LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY]
[LOBBY, BATHROOM, UTILITY]
[STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN]
使用EnumMap
interface Command {
void action();
}
enum AlarmPoints {STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,OFFICE4, BATHROOM, UTILITY, KITCHEN};
public class EnumMaps {
public static void main(String[] args) {
EnumMap em = new EnumMap(AlarmPoints.class);
em.put(AlarmPoints.KITCHEN, new Command() {
public void action() {
System.err.println("Kitchen fire!");
}
});
em.put(AlarmPoints.BATHROOM, new Command() {
public void action() {
System.err.println("Bathroom alert!");
}
});
for (Map.Entry e : em.entrySet()) {
System.err.println(e.getKey() + ": ");
e.getValue().action();
}
try { // If there's no value for a particular key:
em.get(AlarmPoints.UTILITY).action();
} catch (Exception e) {
System.err.println(e);
}
}
}
运行结果如下:
BATHROOM:
Bathroom alert!
KITCHEN:
Kitchen fire!
java.lang.NullPointerException
综合枚举应用,我们可利用枚举来设计责任链,状态机或多路分发等。
如下是一个处理邮件的实例:
class Mail {
// The NO's lower the probability of random selection:
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 "Mail " + id;
}
public String details() {
return toString() + ", General Delivery: " + generalDelivery + ", Address Scanability: " + scannability + ", Address Readability: " + readability +
", Address Address: " + address + ", Return address: " + returnAddress;
}
// Generate test Mail:
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;
}
public static Iterable generator(final int count) {
return new Iterable() {
int n = count;
public Iterator iterator() {
return new Iterator() {
public boolean hasNext() {
return n-- > 0;
}
public Mail next() {
return randomMail();
}
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
};
}
}
public class PostOffice {
enum MailHandler {
GENERAL_DELIVERY {
boolean handle(Mail m) {
switch (m.generalDelivery) {
case YES:
System.err.println("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:
System.err.println("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:
System.err.println("Delivering " + m + " normally");
return true;
}
}
}
},
RETURN_TO_SENDER {
boolean handle(Mail m) {
switch (m.returnAddress) {
case MISSING:
return false;
default:
System.err.println("Returning " + m + " to sender");
return true;
}
}
};
abstract boolean handle(Mail m);
}
static void handle(Mail m) {
for (MailHandler handler : MailHandler.values())
if (handler.handle(m))
return;
System.err.println(m + " is a dead letter");
}
public static void main(String[] args) {
for (Mail mail : Mail.generator(10)) {
System.err.println(mail.details());
handle(mail);
System.err.println("*****");
}
}
}
运行结果如下:
Mail 0, General Delivery: NO2, Address Scanability: UNSCANNABLE, Address Readability: YES3, Address Address: OK1, Return address: OK1
Delivering Mail 0 normally
*****
Mail 1, General Delivery: NO5, Address Scanability: YES3, Address Readability: ILLEGIBLE, Address Address: OK5, Return address: OK1
Delivering Mail 1 automatically
*****
Mail 2, General Delivery: YES, Address Scanability: YES3, Address Readability: YES1, Address Address: OK1, Return address: OK5
Using general delivery for Mail 2
*****
Mail 3, General Delivery: NO4, Address Scanability: YES3, Address Readability: YES1, Address Address: INCORRECT, Return address: OK4
Returning Mail 3 to sender
*****
Mail 4, General Delivery: NO4, Address Scanability: UNSCANNABLE, Address Readability: YES1, Address Address: INCORRECT, Return address: OK2
Returning Mail 4 to sender
*****
Mail 5, General Delivery: NO3, Address Scanability: YES1, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK2
Delivering Mail 5 automatically
*****
Mail 6, General Delivery: YES, Address Scanability: YES4, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK4
Using general delivery for Mail 6
*****
Mail 7, General Delivery: YES, Address Scanability: YES3, Address Readability: YES4, Address Address: OK2, Return address: MISSING
Using general delivery for Mail 7
*****
Mail 8, General Delivery: NO3, Address Scanability: YES1, Address Readability: YES3, Address Address: INCORRECT, Return address: MISSING
Mail 8 is a dead letter
*****
Mail 9, General Delivery: NO1, Address Scanability: UNSCANNABLE, Address Readability: YES2, Address Address: OK1, Return address: OK4
Delivering Mail 9 normally
*****
一个状态机可以根据输入在有限个状态之间移动,然后在满足某种状态之后结束工作,另外每个状态都会有相应的输出,一个售货机器是一个典型的状态机的实例,如下:
enum Input { //售货机可以接收的钞票金额和所有商品的价格
NICKEL(5), //五分
DIME(10), //一角
QUARTER(25), //两角五分
DOLLAR(100),//一块
TOOTHPASTE(200), //药膏2元
CHIPS(75), //炸薯条75美分
SODA(100),// 苏打水1元
SOAP(50), //肥皂5毛
ABORT_TRANSACTION {
public int amount() {
throw new RuntimeException("退出时不能获取余额!");
}
},
STOP {
public int amount() {
throw new RuntimeException("关机时不能获取余额!");
}
};
//金额
int value;
Input(int value) {
this.value = value;
}
Input() {
}
//返回该操作项的金额
int amount() {
return value;
}
/**
* @return 随机获取的操作
*/
public static Input randomSelection() {
return values()[new Random(System.nanoTime()).nextInt(values().length)];
}
}
enum Category { //自动售货机的状态
MONEY(Input.NICKEL, Input.DIME, Input.QUARTER, Input.DOLLAR), //放入钞票*
ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS, Input.SODA, Input.SOAP), //选择商品
QUIT_TRANSACTION(Input.ABORT_TRANSACTION), //退出
SHUT_DOWN(Input.STOP); //关机
private Input[] values;
Category(Input... types) {
values = types;
}
public static EnumMap categories = new EnumMap(Input.class);
public Input[] getValues() {
return values;
}
//初始化自动售货机状态集合
static {
for (Category c : Category.class.getEnumConstants()) {
for (Input input : c.values) {
categories.put(input, c);
}
}
}
/**
* 返回该操作项所属状态*
*/
public static Category categorize(Input input) {
return categories.get(input);
}
}
//自动售货机:VendingMachine用来对输入进行相应,首先通过Category枚举归类输入,然后使用switch语句
public class VendingMachine {
//当前运行状态
private static State state = State.RESTING;
//当前余额
private static int amount = 0;
//当前选择商品
private static Input selection = null;
enum StateDuration { //持续状态,不能做其他操作
TRANSIENT
}
enum State { //运行状态
RESTING {
void next(Input input) {
switch (Category.categorize(input)) {
case MONEY:
amount += input.amount();
System.out.println("放入金额:" + input.amount() + "美分");
state = ADDING_MONEY;
break;
case SHUT_DOWN:
state = TERMINAL;
break;
default:
state = RESTING;
break;
}
}
},
ADDING_MONEY { //选择商品
void next(Input input) {
switch (Category.categorize(input)) {
case MONEY:
amount += input.amount();
System.out.println("再次放入金额:" + input.amount() + "美分,您的余额是:" + amount + "美分");
break;
case ITEM_SELECTION:
selection = input;
System.out.println("选择商品:" + input);
if (amount < input.amount()) {
System.out.println("你的余额不够购买商品:" + input);
state = ADDING_MONEY;
} else state = DISPENSING;
break;
case QUIT_TRANSACTION:
state = GIVING_CHANGE;
break;
case SHUT_DOWN:
state = TERMINAL;
break;
default:
state = ADDING_MONEY;
break;
}
}
},
DISPENSING(StateDuration.TRANSIENT) { //发出商品,交易成功
void next() {
System.out.println("交易成功!请拿好您的商品:" + selection);
//扣除购买商品的金额
amount -= selection.amount();
state = GIVING_CHANGE;
}
},
GIVING_CHANGE(StateDuration.TRANSIENT) { //找零
void next() {
if (amount > 0) {
System.out.println("请拿好您的找零:" + amount + "美分");
amount = 0;
}
state = TERMINAL;
}
},
TERMINAL { //交易终止
void output() {
System.out.println("交易结束");
}
};
private boolean isTransient = false;
public boolean isTransient() { //当前是否是瞬时状态(即不可以做其他操作)
return this.isTransient;
}
State() {
}
State(StateDuration stateDuration) {
this.isTransient = true;
}
//默认方法(在瞬时状态时做其他操作时被调用)
void next(Input input) {
System.out.println("该状态不能做其他操作!");
}
//默认方法(在非瞬时状态时不做操作时被调用)
void next() {
System.out.println("请选择一个操作!");
}
//默认方法(查看余额)
void output() {
System.out.println("您的余额还剩:" + amount + "美分");
}
}
//执行一个操作
public static void run(Input gen) {
if (state != State.TERMINAL) {
if (state.isTransient()) {
state.next();
} else {
state.next(gen);
}
} else {
state.output();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int i = 0;
while (true) {
switch (state) {
case RESTING:
run(Enums.random(Category.MONEY.getValues()));
break;
case ADDING_MONEY:
//如果金额不足
if (i > 0) {
run(Enums.random(Category.MONEY.getValues()));
i = 0;
} else {
run(Enums.random(Category.ITEM_SELECTION.getValues()));
i++;
}
break;
case TERMINAL:
run(Input.STOP);
return;
default:
run(null);
break;
}
}
}
}
运行结果如下:
放入金额:25美分
选择商品:CHIPS
你的余额不够购买商品:CHIPS
再次放入金额:25美分,您的余额是:50美分
选择商品:TOOTHPASTE
你的余额不够购买商品:TOOTHPASTE
再次放入金额:5美分,您的余额是:55美分
选择商品:SODA
你的余额不够购买商品:SODA
再次放入金额:5美分,您的余额是:60美分
选择商品:CHIPS
你的余额不够购买商品:CHIPS
再次放入金额:25美分,您的余额是:85美分
选择商品:SODA
你的余额不够购买商品:SODA
再次放入金额:10美分,您的余额是:95美分
选择商品:SOAP
交易成功!请拿好您的商品:SOAP
请拿好您的找零:45美分
交易结束
参考:http://www.jb51.net/article/31951.htm