以下笔记均出自 Thinking in java。
关键词enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。
1 基本的enum特性
values() : 遍历enum实例,返回enum实例的数组,数组中的元素严格保持其在enum中声明的顺序。
ordinal(): 返回一个int值,这是每个enum实例在声明时的顺序,从0开始。
equals()、hashCode()、compareTo():编译器自动提供的方法,因Enum类实现了Comparable接口,所以具有compareTo()方法,同时,它还实现了Serializable接口。
getDeclaringClass():获取实例所属的enum类。
name():返回实例enum实例声明时的名字,与toString()方法 效果相同。
使用例子 : https://github.com/xu509/Java-practise/blob/master/src/enumerated/EnumClass.java
1.1 将静态导入用于enum
使用静态导入(static import)可以将enum实例的标示符带入当前的命名空间,大部分的情况下,这种使用方法是方便的,但会增加代码复杂度。例子://: enumerated/Spiciness.java package enumerated; public enum Spiciness { NOT, MILD, MEDIUM, HOT, FLAMING } ///:~
注意,在定义enum的同一个文件中,这种技巧无法使用;如果是在默认包中定义enum,这种技巧也无法使用。(暂不理解)//: enumerated/Burrito.java package enumerated; 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) { System.out.println(new Burrito(NOT)); System.out.println(new Burrito(MEDIUM)); System.out.println(new Burrito(HOT)); } } /* Output: Burrito is NOT Burrito is MEDIUM Burrito is HOT *///:~
2 向enum中添加新方法
除了不能继承自一个enum之外,我们基本上可以把一个enum看做常规类,我们可以添加方法,甚至是main方法。例:注意: 1、如果打算定义自己的方法,就必须要在enum实例序列的最后添加一个分号。public enum OzWitch { // Instances must be defined first, before methods: WEST("Miss Gulch, aka the Wicked Witch of the West"); 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()); } }
2、必须先定义enum实例,后定义任何方法或属性,否则编译报错。3、enum的构造器可访问性局限在private或者包属性,一旦该类编译结束,编译器就不允许我们再使用其构造器创建任何实例
2.1 覆盖enum方法
这里有一个例子,获取枚举属性的首字母大写字符串
public enum SpaceShip { SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP; 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); } } }
3 switch语句中的enum
在switch语句中使用enum,是enum提供的一项很遍历的功能。可提供一个小型状态机。
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;
}
}
完整代码:https://github.com/xu509/Java-practise/blob/master/src/enumerated/TrafficLight.java
4 values()神秘之处
所有的enum类都继承自Enum类,但是Enum类中没有values()方法,于是我们利用反射来观察其中究竟:
详细代码: https://github.com/xu509/Java-practise/blob/master/src/enumerated/Reflection.java
values()是由编译器添加的static方法。
由于Enum没有values()方法,所以通过反射获取枚举类时,即使将实例上转型成Enum也无法获取每一个enum实例,这时候可以用Class中的getEnumConstants()方法。
package enumerated;//: enumerated/UpcastEnum.java
// No values() method if you upcast an 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);
}
}
5 实现、而非继承
由于所有的enum都继承自java.lang.Enum类,而Java不支持多重继承,所以你的enum不能再继续其他类。
而我们创建一个新的enum时,可以同时实现一个或多个接口。
示例 :https://github.com/xu509/Java-practise/blob/master/src/enumerated/cartoons/EnumImplementation.java
6 随机选取
设计一个工具类,能随机选取枚举类中的枚举实例。
//: net/mindview/util/Enums.java
package net.mindview.util;
import java.util.Random;
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)];
}
} ///:~
使用示例:
package enumerated;//: enumerated/RandomTest.java
import net.mindview.util.*;
enum Activity { SITTING, LYING, STANDING, HOPPING,
RUNNING, DODGING, JUMPING, FALLING, FLYING }
public class RandomTest {
public static void main(String[] args) {
for(int i = 0; i < 20; i++)
System.out.print(Enums.random(Activity.class) + " ");
}
}
<T extends Enum<T>>表示T是一个enum的实例。
7 使用接口组织枚举
无法从enum继承子类令人沮丧,这种需求源自我们希望扩展原enum中的元素,有时希望将一个enum中的元素进行分组。
在一个接口内部,创建该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。
假如我们想用enum来表示不同的食物,同时希望每个enum元素仍然保持Food类型。
package enumerated.menu;
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好用了
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);
}
}
如这个例子,每一个Course实例都将其对应的Class对象作为构造器,通过getEnumConstants()方法,可以从该Class对象中取得某个Food子类的所有enum实例。
调用生成菜单:
更清晰的代码编写:
package enumerated.menu;
public class Meal {
public static void main(String[] args) {
for(int i = 0; i < 5; i++) {
for(Course course : Course.values()) {
Food food = course.randomSelection();
System.out.println(food);
}
System.out.println("---");
}
}
}
更清晰的代码编写:
num 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());
}
}
}
8 使用EnumSet替代标志
Java SE5引入EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。
例子:
//: enumerated/AlarmPoints.java
package enumerated;
public enum AlarmPoints {
STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
OFFICE4, BATHROOM, UTILITY, KITCHEN
} ///:~
package enumerated;
import java.util.*;
import static enumerated.AlarmPoints.*;
import static net.mindview.util.Print.*;
public class EnumSets {
public static void main(String[] args) {
EnumSet<AlarmPoints> points =
EnumSet.noneOf(AlarmPoints.class); // Empty set
points.add(BATHROOM);
print(points);
points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
print(points);
points = EnumSet.allOf(AlarmPoints.class);
points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
print(points);
points.removeAll(EnumSet.range(OFFICE1, OFFICE4));
print(points);
points = EnumSet.complementOf(points);
print(points);
}
} /* Output:
[BATHROOM]
[STAIR1, STAIR2, BATHROOM, KITCHEN]
[LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY]
[LOBBY, BATHROOM, UTILITY]
[STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN]
*///:~
9 使用EnumMap
EnumMap是一种特殊的Map,它要求其中的键(key)必须来自一个enum。
以下是EnumMap配合命令模式的例子:
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);
}
}
} /* Output:
BATHROOM: Bathroom alert!
KITCHEN: Kitchen fire!
java.lang.NullPointerException
*///:~
10 常量相关的方法
要实现常量相关的方法,你需要为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());
}
} /* (Execute to see output) *///:~
另一个例子:涉及abstract方法以及EnumSet特性,
https://github.com/xu509/Java-practise/blob/master/src/enumerated/CarWash.java
覆盖常量相关方法例子:
public enum OverrideConstantSpecific {
NUT, BOLT,
WASHER {
void f() {
print("Overridden method");
}
};
void f() {
print("default behavior");
}
public static void main(String[] args) {
for (OverrideConstantSpecific ocs : values()) {
printnb(ocs + ": ");
ocs.f();
}
}
} /* Output:
NUT: default behavior
BOLT: default behavior
WASHER: Overridden method
*///:~
10.1 使用enum的职责链
在职责链(Chain of Responsibility)设计模式中,我们以不同的方式来解决一个问题,然后将它们链接到一起。当一个请求到来时,它遍历整个链,直到链中的某个解决方案能够处理该请求。https://github.com/xu509/Java-practise/blob/master/src/enumerated/PostOffice.java
10.2 使用enum的状态机
枚举类型非常适合用来创建状态机。一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到另一个状态,不过也可能存在瞬时状态,一旦任务执行结束,状态机就会立刻离开瞬时状态。例子:https://github.com/xu509/Java-practise/blob/master/src/enumerated/Input.javahttps://github.com/xu509/Java-practise/blob/master/src/enumerated/VendingMachine.java
11 多路分发
Java只支持单路分发。也就是说,如果要执行的操作包含不止一个位置的对象时,Java的动态绑定机制只能处理其中一个的类型。我们必须要自己判定其他类型,从而实现自己的动态绑定(暂时不理解)。
解决这个问题的办法是多路分发。多态只能发生在方法调用时,所以,如果你想使用两路分发,那么就必须有两个方法调用:第一个方法调用决定第一个位置类型,第二个方法调用决定第二个位置的类型。
以下是一个多路分发的例子:https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo1.java
11.1 使用enum分发
使用enum实现多路分发,例子:https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo2.java
接口 :https://github.com/xu509/Java-practise/blob/master/src/enumerated/Competitor.java
工具类:https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo.java
11.2 使用常量相关的方法
使用常量相关方法完成分发:例子1,https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo3.java
优化后:https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo4.java
11.3 使用EnumMap分发
实现例子: https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo5.java
11.4 使用二维数组
https://github.com/xu509/Java-practise/blob/master/src/enumerated/RoShamBo6.java