关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。
1. 基本enum特性
values()返回enum实例的数组,而且保持声明的顺序:
enum Shrubbery {GROUND, CRAWLING, HANGING}
public class EnumClass {
public static void main(String[] args) {
for (Shrubbery s : Shrubbery.values()) {
print(s + " ordinal: " + s.ordinal());
printnb(s.compareTo(Shrubbery.CRAWLING) + " ");
printnb(s.equals(Shrubbery.CRAWLING) + " ");
print(s == Shrubbery.CRAWLING);
print(s.getDeclaringClass());
print(s.name());
print("----------------------");
}
// Produce an enum value from a string name:
for (String s : "HANGING CRAWLING GROUND".split(" ")) {
Shrubbery shrub = Enum.valueOf(Shrubbery.class, s);
print(shrub);
}
}
}
运行结果:
GROUND ordinal: 0
-1 false false
class Shrubbery
GROUND
----------------------
CRAWLING ordinal: 1
0 true true
class Shrubbery
CRAWLING
----------------------
HANGING ordinal: 2
1 false false
class Shrubbery
HANGING
----------------------
HANGING
CRAWLING
GROUND
- ordinal方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。
- ==来比较enum实例,编译器会自动提供equals和hashCode方法。
- getDeclaringClass()获取其所属的enum类。
- name()返回enum实例声明时的名字,与使用toString()效果相同。
- valueOf()实在Enum中定义的static方法,他根据给定的名字返回相应的enum实例,如果不存在会抛出异常。
1.1 将静态导入用于enum
使用static import能够将enum实例的标识符代入当前的命名空间,所以无需再用enum类型来修饰enum实例。唯一担心的是使用静态导入会不会导致代码令人难以理解。
1.2 向enum中添加新方法
除了不能继承自一个enum之外,基本上可以将enum看做一个常规类。也就是说,可以添加方法,甚至可以有main方法。
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());
}
}
}
必须在enum实例序列的最后添加一个分号。Java要求必须先定义enum实例,如果在实例之前定义任何方法或属性,编译时会报错。有意将构造器声明为private,但对于他的可访问性并没有什么影响,因为即使不声明为private,我们只能在enum内部使用其构造器创建enum实例。一旦enum的定义结束,编译器就不允许在使用其构造器来创建任何实例了。
1.3 覆盖enum的方法
public enum SpaceShip {
SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP;
@Override
public String toString() {
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);
}
}
}
2. switch语句中的enum
一般来说switch中只能使用整形值,而枚举类型天生就具备整形值的次序,并且可以通过ordinal()方法获取其次序。
enum Signal {
GREEN, YELLOW, RED,
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
// Note that you don't have to say Signal.RED
// in the case statement:
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();
}
}
}
3.values()的神秘之处
enum类都继承自Enum类,我们可以查看Enum中并没有values()方法。利用反射机制查看究竟:
values()是由编译器添加的static方法。同时创建Explore的过程中,编译器还添加了valueOf()方法。不是Enum类不是已经有valueOf()方法了吗,不过Enum中的ValueOf()方法需要两个参数,这个新增方法只需一个参数。由于Set只存储方法的名字,不考虑签名,所以removeAll只剩下values。
由于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() 获取所有Enum对象的实例
5. 实现,而非继承
创建一个新的enum,可以同时实现一个或多个接口
enum CartoonCharacter implements Generator<CartoonCharacter> {
SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;
private Random rand = new Random(47);
@Override
public CartoonCharacter next() {
return values()[rand.nextInt(values().length)];
}
}
public class EnumImplementation {
public static <T> void printNext(Generator<T> rg) {
System.out.print(rg.next() + ", ");
}
public static void main(String[] args) {
// Choose any instance:
CartoonCharacter cc = CartoonCharacter.BOB;
for (int i = 0; i < 10; i++) {
printNext(cc);
}
}
} /*
BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY, NUTTY, SLAPPY,
*/
6. 随机选取
7. 使用接口组织枚举
有时希望使用子类将一个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;
}
}
8. 使用EnumSet替代标志
Set是一种集合不能添加重复元素。enum也要求其成员是唯一的。
Java SE5引入了EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。这种标志可以用来表示某种开关信息,不过,使用这种标志,最终操作的只是一些bit。使用EnumSet的有点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。
EnumSet 包含的使用方法:
EnumSet<AlarmPoints> points =EnumSet.noneOf(AlarmPoints.class); // Empty set
points.addAll() 添加所有Enum元素
EnumSet.of() 返回参数中添加的Enum元素集合
points.removeAl() 移除参数中包含的Enum元素集合
EnumSet.complementOf() 用于创建包含与指定的Enum_Set类型相同的元素的EnumSet
研究EnumSet文档,会发现of()方法被重载了很多次,不但为可变数量参数进行了重载,而且为接受2至5个显式的参数的情况都进行了重载。这也从侧面表现了EnumSet对性能的关注。
9. 使用EnumMap
EnumMap要求其中的键必须来自于一个enum由于enum本身的限制,所以EnumMap内部是数组实现。因此EnumMap速度很快,可以放心进行查找操作。
EnumMaps 包含的方法
EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
interface Command {
void action();
}
public class EnumMaps {
public static void main(String[] args) {
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 { // If there's no value for a particular key:
em.get(UTILITY).action();
} catch (Exception e) {
print(e);
}
}
}
与EnumSet一样,enum实例定义时的次序决定了其在EnumMap中的顺序。
main()方法的最后部分说明,enum的每一个实例作为一键,总是存在的。但是,如果没有为这个键调用put()方法来存入相应的值的话,其对应的值就是null。
10 常量相关的方法
**在Enum中定义的元素都是Enum类中的各个实例对象。每个Enum元素都是一个Enum类型的staic final类型对象。**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());
}
}
}