学习总结
本篇是对学习【Java编程思想 第19章 枚举】的学习总结。
关键字enum可以将一组具名的值得有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种有用的功能。
- 基本enum的特性
常用方法:
函数名 | 含义 |
---|---|
compareTo() | 比较此枚举对象与指定对象的顺序 |
equals() | 当指定对象等于此枚举常量时,返回true |
getDeclaringClass() | 返回此枚举常量的枚举类型想对应的Class对象 |
name() | 返回此枚举常量的名称,在其枚举生命中对其进行声明 |
ordinal() | 返回此枚举对象常量的序数(它在枚举声明中的位置,其中初始常量序数为零) |
toString() | 返回枚举常量的名称,它包含在声明中 |
valueOf() | 返回带指定名称的指定枚举类型的枚举常量 |
还有values()和valueOf()在之后会介绍
enum Shrubbery{
GROUND, CRAWLING, HANGING
}
public class EnumClass {
public static void main(String[] args) {
for ( Shrubbery s :
Shrubbery.values()) {
System.out.println(s + " ordinal: " + s.ordinal());
System.out.println(s.equals(Shrubbery.HANGING));
System.out.println(s.compareTo(Shrubbery.GROUND));
System.out.println(s == Shrubbery.CRAWLING);
// 返回与此枚举常量的枚举类型相对应的 Class 对象
System.out.println(s.getDeclaringClass());
// 返回此枚举常量的名称,在其枚举声明中对其进行声明
System.out.println(s.name());
System.out.println("---------------------");
}
for ( String s : "GROUND CRAWLING HANGING".split("\\s+") ) {
// 返回带指定名称的指定枚举类型的枚举常量
Shrubbery shrubbery = Enum.valueOf(Shrubbery.class, s);
System.out.println(shrubbery);
}
}
}
在enum添加或覆盖方法
添加方法
如果你打算定义自己的方法,那么必须在enum实例序列的最后添加一个分号。Java要求你必须先先定义enum实例。如果在定义enum实例之前定义了任何方法或属性,那么编译器会报错。
public enum OzWith { // 带构造函数的枚举 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 interface, but missing"); private String description; // 不允许使用public/protected OzWith(String description) { this.description = description; } public String getDescription() { return description; } public static void main(String[] args) { for ( OzWith ozwith : OzWith.values()) { System.out.println(ozwith + ": " + ozwith.getDescription()); } } }
覆盖方法
覆盖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 ship : SpaceShip.values() ) { System.out.println(ship); } } }
values()和valueOf()
你在JDK文档是找不到values这个方法的说明的,不信你大可试试,但是在IDE中,你却被提示有这个方法,这是怎么回事呢?
事实上,values()是有编译器添加的static方法。此外,编译器还为其添加了valueOf()。你可能会问,enum类不是已经有valueOf()方法了吗?这里编译器为其添加的方法接受一个参数,enum类中valueOf()接受两个参数。
enum Explore{
HERE, THERE;
}
// javap Explore
final class Explore extends java.lang.Enum<Explore> {
public static final Explore HERE;
public static final Explore THERE;
public static Explore[] values();
public static Explore valueOf(java.lang.String);
static {};
}
- switch语句中的enum
在switch中使用enum,是enum提供的一项非常便利的功能。枚举天生就具有整数值次序,并且可以通过ordinal方法获得起次序。
enum无法继承
创建enum是,编译器会为你生成一个相关的类,这个类继承java.lang.Enum。而Java不支持多继承,所以enum不能再继承其他的类。
在进一步说,Java内部把每一个enum实例都转换为public static final CartoonCharacter BOB;,由此可以看出enum实例的不可更改以及不能当做Class类型来对待。
enum CartoonCharacter implements Generator<CartoonCharacter> {
SLAPPY, SPANKY, PUNCHY, BOUNCY, NUTTY, BOB;
private Random random = new Random(47);
@Override
public CartoonCharacter next() {
return values()[random.nextInt(values().length)];
}
}
public class EnumImplementation {
public static <T> void printNext(Generator<T> gen) {
System.out.print(gen.next() + " ");
}
public static void main(String[] args) {
CartoonCharacter cc = CartoonCharacter.BOB;
for ( int i = 0; i < 10; i++ ) {
printNext(cc);
}
}
}
- 使用EnumSet替代标志
常用方法:
函数名 | 含义 |
---|---|
allOf() | 创建一个包含指定元素类型的所有元素的枚举Set |
noneOf() | 车间爱你一个具有指定元素类型的空枚举Set |
copyOf | 创建一个其元素类型与指定枚举Set相同的枚举Set |
of | 创建一个最初包含指定元素的枚举 set(很多重载) |
range() | 创建一个最初包含两个指定端点所定义的所有元素的枚举Set |
complementOf() | 创建一个其元素类型与指定枚举 set 相同的枚举 set,最初包含指定 set 中所不 包含的此类型的所有元素(补集) |
public enum AlarmPoints {
STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY, KITCHEN;
}
public class EnumSets {
public static void main(String[] args) {
//System.out.println((75+63)>>>6);
// 创建一个具有指定元素类型的空枚举 set
EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);
points.add(AlarmPoints.BATHROOM);
print(points);
// 创建一个最初包含指定元素的枚举 set
points.addAll(EnumSet.of(AlarmPoints.OFFICE1, AlarmPoints.OFFICE2, AlarmPoints.OFFICE3));
print(points);
// 创建一个包含指定元素类型的所有元素的枚举 set
points.addAll(EnumSet.allOf(AlarmPoints.class));
print(points);
points.removeAll(EnumSet.of(AlarmPoints.OFFICE1, AlarmPoints.OFFICE2));
print(points);
points.removeAll(EnumSet.range(AlarmPoints.OFFICE1, AlarmPoints.OFFICE4));
print(points);
// 创建一个其元素类型与指定枚举 set 相同的枚举 set,最初包含指定 set 中所不 包含的此类型的所有元素
points = EnumSet.complementOf(points);
print(points);
}
}
- 使用EnumMap
interface Command{
void action();
}
public class EnumMaps {
public static void main(String[] args) {
EnumMap<AlarmPoints, Command> em =
new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
// lambda直接写一句话的那种
em.put(AlarmPoints.KITCHEN, ()-> print("Kitchen fire!") );
em.put(AlarmPoints.BATHROOM, ()-> print("Bathroom alert!"));
for ( Map.Entry<AlarmPoints, Command> e :
em.entrySet() ) {
print(e.getKey() + " : ");
e.getValue().action();
}
try {
// 会捕捉到NullPointerException
em.get(AlarmPoints.LOBBY);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 与常量相关的方法
Java的enum有一个非常有趣的特性,即它允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。为此,你需要定义一个或多个abstract方法,然后为每个实例实现这个抽象方法。
public enum ConstantSpecificMethod {
DATE_TIME{
@Override
String getInfo() {
return DateFormat.getDateInstance().format(new Date());
}
},
CLASSPATH{
@Override
String getInfo() {
return System.getenv("CLASSPATH");
}
},
VERSION{
@Override
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下实例是一个枚举类的一个实例对象,这里是内部类。
ConstantSpecificMethod$1.class
ConstantSpecificMethod$2.class
ConstantSpecificMethod$3.class
ConstantSpecificMethod.class
- 使用enum的职责链和枚举机
职责链模式:当一个请求到来时,遍历这个链,知道链中的某个解决方案能够解决处理该请求。
枚举机:一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到另一个状态,不过也可能存在瞬时状态,而一旦任务执行结束,状态机就会立刻离开瞬时状态。
- 多路分发
如果要执行的操作包含了不止一个类型位置的对象时,那么Java的动态绑定机制只能处理其中一个的类型。这时,需要自己来判断其他的类型,从而实现自己的动态绑定。
public enum Outcome {
WIN, LOSE, DRAW;
}
interface Item {
Outcome complete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}
class Paper implements Item {
public Outcome complete(Item it) {return it.eval(this);}
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 complete(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 complete(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";}
}
class RoShamBo {
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.complete(b));
}
public static void main(String[] args) {
for ( int i = 0; i < size ; i++ )
match(newItem(), newItem());
}
}
当然,你也可以在拿到一个不确定类型的对象时,对其进行instanceOf判断。
另外,还有使用enum进行分发,使用常量相关的方法分发,使用EnumMap进行分发,使用二维数组进行分发。
这里只贴上使用二维数组进行分发的代码
// 使用二维数组进行分发
public enum Outcome {
WIN, LOSE, DRAW;
}
public interface Competitor<T extends Competitor<T>> {
Outcome compete(T competitor);
}
public class Game {
private 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));
}
}
public enum RoShamBo implements Competitor<RoShamBo> {
private static Outcome[][] table = {
{DRAW, LOSE, WIN},
{WIN, DRAW, LOSE},
{LOSE, WIN, DRAW}
};
@Override
public Outcome compete(RoShamBo competitor) {
return table[this.ordinal()][competitor.ordinal()];
}
public static void main(String[] args) {
Game.play(RoShamBo.class, 20);
}
}