——总结自《java编程思想》
关键字enum可以将一组具名的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。
枚举简介
基本enum特性:
- values()返回enum实例的数组,而且保持声明的顺序。
- ordinal方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。
- 可以使用==来比较enum实例,编译器会自动提供equals和hashCode方法。Enum类实现了Comparable接口,所以它具有compareTo()方法。同时还实现了Serializable接口。
getDeclaringClass()获取其所属的enum类。 - name()返回enum实例声明时的名字,与使用toString()效果相同。
- valueOf()实在Enum中定义的static方法,它根据给定的名字返回相应的enum实例,如果不存在会抛出异常。
- 可以使用static import将enum实例的标识符代入当前的命名空间,无需再用enum类型来修饰enum实例。
向enum中添加方法:
public enum OzWitch {
// Instances must be defined first, before methods:
WEST("Miss Gulch, aka the Wicked Witch of the West"),
NORTH("Glinda, the Good Witch of the North"),
EAST("Wicked Witch of the East, wearer of the Ruby " +
"Slippers, crushed by Dorothy's house"),
SOUTH("Good by inference, but missing");
private String description;
// Constructor must be package or private access:
private OzWitch(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static void main(String[] args) {
for (OzWitch witch : OzWitch.values()) {
print(witch + ": " + witch.getDescription());
}
}
}
switch方法:
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;
break;
}
}
@Override
public String toString() {
return "The traffic light is " + color;
}
public static void main(String[] args) {
TrafficLight t = new TrafficLight();
for (int i = 0; i < 7; i++) {
print(t);
t.change();
}
}
}
**values神秘之处**:
enum类都继承自Enum类,但是Enum并没有values()方法。利用反射机制查看究竟:
enum Explore {HERE, THERE}
public class Reflection {
public static Set<String> analyze(Class<?> enumClass) {
print("----- Analyzing " + enumClass + " -----");
print("Interfaces:");
for (Type t : enumClass.getGenericInterfaces()) {
print(t);
}
print("Base: " + enumClass.getSuperclass());
print("Methods: ");
Set<String> methods = new TreeSet<String>();
for (Method m : enumClass.getMethods()) {
methods.add(m.getName());
}
print(methods);
return methods;
}
public static void main(String[] args) {
Set<String> exploreMethods = analyze(Explore.class);
Set<String> enumMethods = analyze(Enum.class);
print("Explore.containsAll(Enum)? " +
exploreMethods.containsAll(enumMethods));
printnb("Explore.removeAll(Enum): ");
exploreMethods.removeAll(enumMethods);
print(exploreMethods);
// Decompile the code for the enum:
OSExecute.command("javap Explore");
}
} /*
----- Analyzing class Explore -----
Interfaces:
Base: class java.lang.Enum
Methods:
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]
----- Analyzing class java.lang.Enum -----
Interfaces:
java.lang.Comparable<E>
interface java.io.Serializable
Base: class java.lang.Object
Methods:
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait]
Explore.containsAll(Enum)? true
Explore.removeAll(Enum): [values]
Compiled from "Reflection.java"
final class Explore extends java.lang.Enum{
public static final Explore HERE;
public static final Explore THERE;
public static final Explore[] values();
public static Explore valueOf(java.lang.String);
static {};
}
*/
values()是由编译器添加的static方法。在创建Explore的过程中,编译器还添加了valueOf()方法。虽然Enum类已经有valueOf()方法,不过Enum中的ValueOf()方法需要两个参数,这个新增方法只需一个参数。由于这里使用的Set只存储方法的名字,不考虑签名,所有在调用Expore.removeAll(Enum)之后,就只剩下[values]了。
从最后的输出可以看出,Explore被标记为final类,所以无法继承自enum。 其中还有一个static初始化子句。由于擦边效应,反编译无法得到Enum的完整信息,所以展示的Explore只是一个原始的Enum,而非事实上的Enum< Explore >。
由于values方法是由编译器插入到enum定义中的static方法,所以enum向上转型为Enum,那么values就不可访问了,不过Class中有一个getEnumConstants方法,所以即便Enum接口中没有vlaues方法,仍然可以通过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);
}
}
}
/*
HITHER
YON
*/
甚至可以对不是枚举的类调用getEnumConstants方法(只不过,此时该方法返回null):
public class NonEnum {
public static void main(String[] args) {
Class<Integer> intClass = Integer.class;
try {
for (Object en : intClass.getEnumConstants()) {
System.out.println(en);
}
} catch (Exception e) {
System.out.println(e);
}
}
} /*
java.lang.NullPointerException
*/
所有的enum都继承自java.lang.Enum类。由于java不支持多重继承,所以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;
}
}
public class TypeOfFood {
public static void main(String[] args) {
Food food = Appetizer.SALAD;
food = MainCourse.LASAGNE;
food = Dessert.GELATO;
food = Coffee.CAPPUCCINO;
}
}
如果想创建一个枚举的枚举,可以创建一个新的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);
}
}
EnumSet
为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。这种标志可以用来表示某种开关信息,不过,使用这种标志,最终操作的只是一些bit。EnumSet设计考虑到了速度因素,因为它必须与非常高效的bit相竞争(其操作与hashSet相比,非常地快)。就其内部来言,它是将一个long值作为比特向量,所以EnumSet非常快速高效。使用EnumSet的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。
EnumSet中的元素必须来自一个enum。
在研究EnumSet文档,可以发现of()方法被重载了很多次,不但为可变数量参数进行了重载,而且为接受2至5个显式的参数的情况都进行了重载。这也从侧面表现了EnumSet对性能的关注。因为,其实只使用可变参书已经可以解决整个问题,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当只使用2到5个参数调用of()方法时,可以调用重载过的方法,而当使用一个参数或多过5个参数时,调用的将是使用可变参数的of()方法。
EnumSet的基础是long,一个long值有64位,而一个enum实例只需一位bit表示其是否存在。也就是说,在不超过一个long的表达能力的情况下,EnumSet可以应用于最多不超过64个元素的enum。
EnumMap:要求其中的键必须来自于一个enu。由于enum本身的限制,所以EnumMap在内部可由数组实现,因此EnumMap的速度很快。可以使用enum实例在EnumMap中进行查找操作,不过只能将enum的实例作为键来调用put()方法,其他操作与使用一般的Map差不多。
常量相关的方法
Java的enum有一个非常有趣的特性,即它允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。要实现常量相关的方法,需要为enum定义一个或多个abstract方法,然后为每个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实例,可以调用其上的方法,称为表驱动的代码(tabled riven code)。enum似乎被当做其超类ConstantSpecificMethod来使用,在调用getInfo()方法时,体现出多态的行为。
职责链
职责链设计模式,以多种不同的方式来解决一个问题,然后将它们链接在一起。当请求到来时,他遍历这个链,直到链中的某个解决方案能够处理该请求。
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<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() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
};
}
}
public class PostOffice {
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:
print("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;
}
}
print(m + " is a dead letter");
}
public static void main(String[] args) {
for (Mail mail : Mail.generator(10)) {
print(mail.details());
handle(mail);
print("*****");
}
}
} /*
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对其实例有严格限制,非常适合用来表现不同的状态和输入。一般而言,每个状态都具有一些相关的输出。
多路分发
Java只支持单路分发,也就是说,如果要执行的操作包含了不止一个类型未知的对象时,那么Java的动态绑定机制只能处理其中一个类型。所以必须自己来判定其他的类型,从而实现自己的动态绑定行为。
为了达到效果,需要与多个方法一同工作:因为每个分发都需要一个方法调用。