Java编程思想之十八 枚举类型

时间:2022-09-02 11:07:35

关键字enum可以将一组具名的值的有限集合创建为一种新的类型, 而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能。

18.1 基本enum特性

创建enum时,编译器会为你生成一个相关类,这个类继承自java.lang.Enum。下面演示一些基本功能。

import static net.mindview.util.Print.*;

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);
}
}
} /* Output:
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
*///:~

18.1.1 将静态导入用于enum

import AAA.Spiciness;

import static AAA.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
*///:~

使用static import能够将enum实例的标识符带入房前的命名空间,所有无需再用enum类型来修饰enum实例。

18.2 向enum中添加新方法

除了不能继承自一个enum之外,我们基本上可以把enum看作一个常规类。

现在我们希望每个枚举实例能够返回对自身的描述,而不是仅仅只是默认人的toString()实现,这只能返回枚举实例的名字。为此,可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。

import static net.mindview.util.Print.*;

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());
}
} /* Output:
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
*///:~

如果你打算定义自己的方法,那么必须再enum实例序列的最后添加一个分号。

18.2.1 覆盖enum的方法

//: enumerated/OzWitch.java
// The witches in the land of Oz.
import static net.mindview.util.Print.*; 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());
}
} /* Output:
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
*///:~

18.3 switch语句中的enum

在switch中,只能使用整数值,而枚举实例天生就具备整数值序列,并且可以通过ordinal()方法取得其次序,因此我们可以在switch语句中使用enum。

//: enumerated/TrafficLight.java
// Enums in switch statements.
import static net.mindview.util.Print.*; // Define an enum type:
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;
}
}
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();
}
}
} /* Output:
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
*///:~

18.4 values()的神秘之处

Enum类中没有values方法,下面利用反射就可以证明这一点:

//: enumerated/Reflection.java
// Analyzing enums using reflection.
import java.lang.reflect.*;
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; 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");
}
} /* Output:
----- 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()方法需要两个参数,而新添加的只需要一个参数。

由于values()方法由编译器插入到enum定义了static方法,如果你将enum实例向上转型为Enum,那么values()方法就不可访问了。在class中有一个getEnumConstants方法,所有即便Enum接口中没有values()方法,我们还是可以通过class对象取得所有enum实例:

//: 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);
}
} /* Output:
HITHER
YON
*///:~

因为getEnumConstants()是Class上的方法,所以你甚至可以对不是枚举的类调用此方法:

//: enumerated/NonEnum.java

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);
}
}
} /* Output:
java.lang.NullPointerException
*///:~

18.5 实现,而非继承

所有的enum都继承自java.lang.Enum类。所有enum不能再继承其他类,然而,我们可以创建一个新的enum时,同时实现一个或多个接口:

//: enumerated/cartoons/EnumImplementation.java
// An enum can implement an interface
package cartoons;
import java.util.*;
import net.mindview.util.*; enum CartoonCharacter
implements Generator<CartoonCharacter> {
SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;
private Random rand = new Random(47);
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);
}
} /* Output:
BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY, NUTTY, SLAPPY,
*///:~

18.7 使用接口组织枚举

在一个接口内部,创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。

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;
}
} ///:~

对enum而言,实现接口是使其子类化唯一办法,所以嵌入在Food中的每个enum都实现了Food接口。

import static menu.Food.*;

public class TypeOfFood {
public static void main(String[] args) {
Food food = Appetizer.SALAD;
food = MainCourse.LASAGNE;
food = Dessert.GELATO;
food = Coffee.CAPPUCCINO;
}
}

当你需要与一大推类型打交道时,接口就不如enum好用了

import net.mindview.util.*;

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);
}
}
//: enumerated/menu/Meal.java
package 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("---");
}
}
} /* Output:
SPRING_ROLLS
VINDALOO
FRUIT
DECAF_COFFEE
---
SOUP
VINDALOO
FRUIT
TEA
---
SALAD
BURRITO
FRUIT
TEA
---
SALAD
BURRITO
CREME_CARAMEL
LATTE
---
SOUP
BURRITO
TIRAMISU
ESPRESSO
---
*///:~

还有一种更简洁的管理枚举的办法,就是将一个enum嵌套在另一个enum内:

import net.mindview.util.*;

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());
}
}
} /* Output:
BOND: MUNICIPAL
BOND: MUNICIPAL
STOCK: MARGIN
STOCK: MARGIN
BOND: JUNK
STOCK: SHORT
STOCK: LONG
STOCK: LONG
BOND: MUNICIPAL
BOND: JUNK
*///:~

18.8 使用EnumSet替代标志

Set是一种集合,只能向其中添加不重复的对象。enum也要求其成员是唯一的,所以enum看起来也具有集合的行为。Java SE5引入EnumSet,是为了通过enum创建一种替代品,以替代传统基于int的“位标志”。

EnumSet中元素必须来自一个enum,我们用EnumSet来跟踪报警的状态:

public enum AlarmPoints {
STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
OFFICE4, BATHROOM, UTILITY, KITCHEN
} ///:~
//: enumerated/EnumSets.java
// Operations on EnumSets
import java.util.*; 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(AlarmPoints.BATHROOM);
print(points);
points.addAll(EnumSet.of(AlarmPoints.STAIR1, AlarmPoints.STAIR2, AlarmPoints.KITCHEN));
print(points);
points = EnumSet.allOf(AlarmPoints.class);
points.removeAll(EnumSet.of(AlarmPoints.STAIR1, AlarmPoints.STAIR2, AlarmPoints.KITCHEN));
print(points);
points.removeAll(EnumSet.range(AlarmPoints.OFFICE1, AlarmPoints.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]
*///:~

EnumSet的基础是long,一个long是64位,一个enum实例只需要一位bit表示其是否存在。在已超过一个long的表达能力的情况下,EnumSet可以应用于最多不超过64个元素:

//: enumerated/BigEnumSet.java
import java.util.*; public class BigEnumSet {
enum Big { A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10,
A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21,
A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32,
A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43,
A44, A45, A46, A47, A48, A49, A50, A51, A52, A53, A54,
A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65,
A66, A67, A68, A69, A70, A71, A72, A73, A74, A75 }
public static void main(String[] args) {
EnumSet<Big> bigEnumSet = EnumSet.allOf(Big.class);
System.out.println(bigEnumSet);
}
} /* Output:
[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45, A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67, A68, A69, A70, A71, A72, A73, A74, A75]
*///:~

EnumSet可以应用多余64个元素的enum,所以可能在必要的时候会添加一个long。

18.9 使用EnumMap

EnumMap是一种特殊的Map,它要求其中的键必须来自一个enum。由于enum本身的限制,所以EnumMap在内部可由数组实现。

//: enumerated/EnumMaps.java
// Basics of EnumMaps.
import java.util.*; import static net.mindview.util.Print.*; 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(AlarmPoints.KITCHEN, new Command() {
public void action() { print("Kitchen fire!"); }
});
em.put(AlarmPoints.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(AlarmPoints.UTILITY).action();
} catch(Exception e) {
print(e);
}
}
} /* Output:
BATHROOM: Bathroom alert!
KITCHEN: Kitchen fire!
java.lang.NullPointerException
*///:~

与EnumSet一样,enum实例定义时的次序决定了其在EnumMap中的顺序。

18.10 常量相关方法

enum允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为:

//: enumerated/ConstantSpecificMethod.java
import java.util.*;
import java.text.*; 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) *///:~

通过相应的enum实例,我们可以调用其上的方法。这通常也称为表驱动的代码。

我们并不能真的将enum实例作为一个类型来使用:

//: enumerated/NotClasses.java
// {Exec: javap -c LikeClasses}
import static net.mindview.util.Print.*; enum LikeClasses {
WINKEN { void behavior() { print("Behavior1"); } },
BLINKEN { void behavior() { print("Behavior2"); } },
NOD { void behavior() { print("Behavior3"); } };
abstract void behavior();
} public class NotClasses {
// void f1(LikeClasses.WINKEN instance) {} // Nope
} /* Output:
Compiled from "NotClasses.java"
abstract class LikeClasses extends java.lang.Enum{
public static final LikeClasses WINKEN;
public static final LikeClasses BLINKEN;
public static final LikeClasses NOD;
...
*///:~

编译器不允许我们将一个enum实例当作一个class类型。因为每个enum元素都是一个LikeClasses类型的static final实例。

它们是static实例,所以无法访问外部类的非static元素或方法,所以对于内部的enum的实例而言,其行为与一般的内部类并不相同。

//: enumerated/CarWash.java
import java.util.*;
import static net.mindview.util.Print.*; public class CarWash {
public enum Cycle {
UNDERBODY {
void action() { print("Spraying the underbody"); }
},
WHEELWASH {
void action() { print("Washing the wheels"); }
},
PREWASH {
void action() { print("Loosening the dirt"); }
},
BASIC {
void action() { print("The basic wash"); }
},
HOTWAX {
void action() { print("Applying hot wax"); }
},
RINSE {
void action() { print("Rinsing"); }
},
BLOWDRY {
void action() { print("Blowing dry"); }
};
abstract void action();
}
EnumSet<Cycle> cycles =
EnumSet.of(Cycle.BASIC, Cycle.RINSE);
public void add(Cycle cycle) { cycles.add(cycle); }
public void washCar() {
for(Cycle c : cycles)
c.action();
}
public String toString() { return cycles.toString(); }
public static void main(String[] args) {
CarWash wash = new CarWash();
print(wash);
wash.washCar();
// Order of addition is unimportant:
wash.add(Cycle.BLOWDRY);
wash.add(Cycle.BLOWDRY); // Duplicates ignored
wash.add(Cycle.RINSE);
wash.add(Cycle.HOTWAX);
print(wash);
wash.washCar();
}
} /* Output:
[BASIC, RINSE]
The basic wash
Rinsing
[BASIC, HOTWAX, RINSE, BLOWDRY]
The basic wash
Applying hot wax
Rinsing
Blowing dry
*///:~

除了实现abstract方法外,还可以覆盖常量相关方法:

import static net.mindview.util.Print.*;

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
*///:~

18.10.1 使用enum的职责链

在职责链设计模式中,程序员以多种不同的方法来决解一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方法能够处理该请求。

//: enumerated/PostOffice.java
// Modeling a post office.
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; 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("*****");
}
}
} /* Output:
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
*****
*///:~

18.10.2 使用enum的状态机

枚举类型非常适合用来创建状态机。一个状态机可以具有有限个状态,它通常根据输入,从一个状态转移到下一个状态,不过也可能存在瞬时状态,而一旦任务执行结束,状态机就会立刻离开瞬时状态。

//: enumerated/Input.java
import java.util.*; public enum Input {
NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),
TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),
ABORT_TRANSACTION {
public int amount() { // Disallow
throw new RuntimeException("ABORT.amount()");
}
},
STOP { // This must be the last instance.
public int amount() { // Disallow
throw new RuntimeException("SHUT_DOWN.amount()");
}
};
int value; // In cents
Input(int value) { this.value = value; }
Input() {}
int amount() { return value; }; // In cents
static Random rand = new Random(47);
public static Input randomSelection() {
// Don't include STOP:
return values()[rand.nextInt(values().length - 1)];
}
} ///:~

下面的例子演示了enum是如何使代码变得更加清晰且易于管理的:


18.11 多路分发

Java只支持单路分发。如果要执行的操作包含了不止一个类型未知的对象时,那么Java动态绑定机制只能处理其中一个的类型。

多路分发:如果你想使用两路分发,那么就必须有两个方法调用:第一个方法调用决定第一个未知类型,第二个方法调用决定了第二个未知类型。要利用多路分发,程序员必须为每一个类型提供一个实际的方法调用,如果你要处理两个不同的类型体系,就需要为每个类型体系执行一个方法调用。

public enum Outcome { WIN, LOSE, DRAW } 

//: enumerated/RoShamBo1.java
// Demonstration of multiple dispatching.
import java.util.*; interface Item {
Outcome compete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}
class Paper implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.DRAW; }
public Outcome eval(Scissors s) { return Outcome.WIN; }
public Outcome eval(Rock r) { return Outcome.LOSE; }
public String toString() { return "Paper"; }
} class Scissors implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.LOSE; }
public Outcome eval(Scissors s) { return Outcome.DRAW; }
public Outcome eval(Rock r) { return Outcome.WIN; }
public String toString() { return "Scissors"; }
} class Rock implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.WIN; }
public Outcome eval(Scissors s) { return Outcome.LOSE; }
public Outcome eval(Rock r) { return Outcome.DRAW; }
public String toString() { return "Rock"; }
} public class RoShamBo1 {
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.compete(b));
}
public static void main(String[] args) {
for(int i = 0; i < SIZE; i++)
match(newItem(), newItem());
}
} /* Output:
Rock vs. Rock: DRAW
Paper vs. Rock: WIN
Paper vs. Rock: WIN
Paper vs. Rock: WIN
Scissors vs. Paper: WIN
Scissors vs. Scissors: DRAW
Scissors vs. Paper: WIN
Rock vs. Paper: LOSE
Paper vs. Paper: DRAW
Rock vs. Paper: LOSE
Paper vs. Scissors: LOSE
Paper vs. Scissors: LOSE
Rock vs. Scissors: WIN
Rock vs. Paper: LOSE
Paper vs. Rock: WIN
Scissors vs. Paper: WIN
Paper vs. Scissors: LOSE
Paper vs. Scissors: LOSE
Paper vs. Scissors: LOSE
Paper vs. Scissors: LOSE
*///:~

18.11.1 使用enum分发

使用构造器来初始化每个enum实例,并以一组结果作为参数:

public interface Competitor<T extends Competitor<T>> {
Outcome compete(T competitor);
} ///:~
import net.mindview.util.*;

public class RoShamBo {
public 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 RoShamBo2 implements Competitor<RoShamBo2> {
PAPER(Outcome.DRAW, Outcome.LOSE, Outcome.WIN),
SCISSORS(Outcome.WIN, Outcome.DRAW, Outcome.LOSE),
ROCK(Outcome.LOSE, Outcome.WIN, Outcome.DRAW);
private Outcome vPAPER, vSCISSORS, vROCK;
RoShamBo2(Outcome paper,Outcome scissors,Outcome rock) {
this.vPAPER = paper;
this.vSCISSORS = scissors;
this.vROCK = rock;
}
public Outcome compete(RoShamBo2 it) {//返回对应对象
switch(it) {
default:
case PAPER: return vPAPER;
case SCISSORS: return vSCISSORS;
case ROCK: return vROCK;
}
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo2.class, 20);
}
}

18.11.2 使用常量相关的方法

常量相关的方法允许我们为每个enum实例提供方法的不同实现。通过这种方式,enum实例虽然可以具有不同的行为,但它们还是不是类型,不能将其作为方法签名中的参数类型来使用,最好的办法是将enum用在switch语句中:

//: enumerated/RoShamBo3.java
// Using constant-specific methods.
public enum RoShamBo3 implements Competitor<RoShamBo3> {
PAPER {
public Outcome compete(RoShamBo3 it) {
switch(it) {
default: // To placate the compiler
case PAPER: return Outcome.DRAW;
case SCISSORS: return Outcome.LOSE;
case ROCK: return Outcome.WIN;
}
}
},
SCISSORS {
public Outcome compete(RoShamBo3 it) {
switch(it) {
default:
case PAPER: return Outcome.WIN;
case SCISSORS: return Outcome.DRAW;
case ROCK: return Outcome.LOSE;
}
}
},
ROCK {
public Outcome compete(RoShamBo3 it) {
switch(it) {
default:
case PAPER: return Outcome.LOSE;
case SCISSORS: return Outcome.WIN;
case ROCK: return Outcome.DRAW;
}
}
};
public abstract Outcome compete(RoShamBo3 it);
public static void main(String[] args) {
RoShamBo.play(RoShamBo3.class, 20);
}
} /* Same output as RoShamBo2.java *///:~

还可以压缩下:

//: enumerated/RoShamBo4.java

public enum RoShamBo4 implements Competitor<RoShamBo4> {
ROCK {
public Outcome compete(RoShamBo4 opponent) {
return compete(SCISSORS, opponent);
}
},
SCISSORS {
public Outcome compete(RoShamBo4 opponent) {
return compete(PAPER, opponent);
}
},
PAPER {
public Outcome compete(RoShamBo4 opponent) {
return compete(ROCK, opponent);
}
};
Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {
return ((opponent == this) ? Outcome.DRAW
: ((opponent == loser) ? Outcome.WIN
: Outcome.LOSE));
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo4.class, 20);
}
} /* Same output as RoShamBo2.java *///:~

18.11.3 使用enumMap分发

使用EnumMap分发能够实现真正的两路分发EnumMap为enum专门设计的一种性能非常好的特殊的Map。由于我们的目的是探索出两种未知的类型,所以可以用一个EnumMap来实现两路分发。

//: enumerated/RoShamBo5.java
// Multiple dispatching using an EnumMap of EnumMaps.
import java.util.*; enum RoShamBo5 implements Competitor<RoShamBo5> {
PAPER, SCISSORS, ROCK;
static EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>>
table = new EnumMap<RoShamBo5,
EnumMap<RoShamBo5,Outcome>>(RoShamBo5.class);
static {
for(RoShamBo5 it : RoShamBo5.values())
table.put(it,
new EnumMap<RoShamBo5,Outcome>(RoShamBo5.class));
initRow(PAPER, Outcome.DRAW, Outcome.LOSE, Outcome.WIN);
initRow(SCISSORS, Outcome.WIN, Outcome.DRAW, Outcome.LOSE);
initRow(ROCK, Outcome.LOSE, Outcome.WIN, Outcome.DRAW);
}
static void initRow(RoShamBo5 it,
Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {
EnumMap<RoShamBo5,Outcome> row =
RoShamBo5.table.get(it);
row.put(RoShamBo5.PAPER, vPAPER);
row.put(RoShamBo5.SCISSORS, vSCISSORS);
row.put(RoShamBo5.ROCK, vROCK);
}
public Outcome compete(RoShamBo5 it) {
return table.get(this).get(it);
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo5.class, 20);
}
} /* Same output as RoShamBo2.java *///:~

18.11.4 使用二维数组

每个enum实例都有一个固定值。所以我们可以使用二维数组,将竞争者映射到竞争关系。采用这种方法能够获得最简洁和最直接的解决方案:

enum RoShamBo6 implements Competitor<RoShamBo6> {
PAPER, SCISSORS, ROCK;
private static Outcome[][] table = {
{ Outcome.DRAW, Outcome.LOSE, Outcome.WIN }, // PAPER
{ Outcome.WIN, Outcome.DRAW, Outcome.LOSE }, // SCISSORS
{ Outcome.LOSE, Outcome.WIN, Outcome.DRAW }, // ROCK
};
public Outcome compete(RoShamBo6 other) {
return table[this.ordinal()][other.ordinal()];//通过ordinal获取枚举实例的值
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo6.class, 20);
}
} ///:~

Java编程思想之十八 枚举类型的更多相关文章

  1. Java编程思想学习&lpar;十四&rpar; 枚举

    关键字enum可以将一组具名的值有限集合创建一种为新的类型,而这些具名的值可以作为常规的程序组件使用. 基本enum特性 调用enum的values()方法可以遍历enum实例,values()方法返 ...

  2. Java编程思想之十四 类型信息

    第十四章 类型信息 运行时类型信息使得你可以在程序运行时发现和使用类型信息 14.1 为什么需要RTTI 面向对象编程中基本的目的是:让代码只操作对基类的引用. 多态: import java.uti ...

  3. Java编程思想学习&lpar;十六&rpar; 并发编程

    线程是进程中一个任务控制流序列,由于进程的创建和销毁需要销毁大量的资源,而多个线程之间可以共享进程数据,因此多线程是并发编程的基础. 多核心CPU可以真正实现多个任务并行执行,单核心CPU程序其实不是 ...

  4. Java编程思想学习&lpar;十五&rpar; 注解

    注解Annotation又叫元数据,是JDK5中引入的一种以通用格式为程序提供配置信息的方式.使用注解Annotation可以使元数据写在程序源码中,使得代码看起来简洁,同时编译器也提供了对注解Ann ...

  5. Java编程思想学习&lpar;十&rpar; 正则表达式

    正则表达式是一种强大的文本处理工具,使用正则表达式我们可以以编程的方法,构造复杂的文本模式,并且对输入的字符串进行搜索.在我看来,所谓正则表达式就是我们自己定义一些规则,然后就可以验证输入的字符串是不 ...

  6. Java编程思想之十二 通过异常处理错误

    Java的基本概念是结构不佳的代码不能运行余下的问题必须在运行期间解决,这就需要错误源能通过某种方式,把适当的信息传递给某个接收者--该接收者将知道如何正确处理这里问题. 12.1 概念 使用异常所带 ...

  7. Java编程思想学习&lpar;十二&rpar; 数组和容器

    一.数组 1).数组的多种初始化方式 下面总结了初始化数组的多种方式,以及如何对指向数组的引用赋值,使其指向另一个数组对象.值得注意的是:对象数组和普通数组的各种操作基本上都是一样的:要说有什么不同的 ...

  8. Java编程思想之十 内部类

    可以将一个类定义放在另一个类的定义内部,这就是内部类. 10.1 创建内部类 创建内部类就是把类的定义置于外部类里面. public class Parcell { class contents{ i ...

  9. java编程思想-枚举类型思维导图

随机推荐

  1. 嵌入式Linux驱动学习之路&lpar;二十七&rpar;字符设备驱动的另一种写法

    之前讲的字符设备驱动程序,只要有一个主设备号,那么次设备号无论是什么都会和同一个 struct file_operations 结构体对应. 而本节课讲的是如何在设备号相同的情况下,让不同的次设备号对 ...

  2. Vue&num;组件

    组件: 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能. 使用: ...

  3. 用外部表的方式查询当天数据库alert日志文件

    1环境准备 2查询ORA-开头的错误

  4. 第四章 Web表单

    4.1 跨站请求伪造保护 安装flask-wtf app = Flask(__name__) app.config['SECRET_KEY'] = 'hard to guess string' 密钥不 ...

  5. Unity NGUI制作scroll view

    unity版本:4.5 NGUI版本:3.6.5 参考链接:http://blog.csdn.net/monzart7an/article/details/23878505,作者:CSDN 冬菊子   ...

  6. grunt轻松入门

    项目目录,js源文件 gruntest Gruntfile.js package.json -- js ext community_plugin.js glogin_frm_cover.js iLog ...

  7. Java并发编程之ThreadGroup

    ThreadGroup是Java提供的一种对线程进行分组管理的手段,可以对所有线程以组为单位进行操作,如设置优先级.守护线程等. 线程组也有父子的概念,如下图: 线程组的创建 public class ...

  8. BigDecimal提供了8种舍入方式

    BigDecimal提供了8种舍入方式 1.ROUND_UP:舍入远离零的舍入模式.在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1).注意,此舍入模式始终不会减少计算值的大小. 2. ...

  9. 消息中间件及WebSphere MQ入门(转载)

    消息队列技术是分布式应用间交换信息的一种技术.消息队列可驻留在内存或磁盘上,队列存储消息直到它们被应用程序读走.通过消息队列,应用程序可独立地执行--它们不需要知道彼此的位置.或在继续执行前不需要等待 ...

  10. &period;NET高级代码审计(第五课) &period;NET Remoting反序列化漏洞

    0x00 前言 最近几天国外安全研究员Soroush Dalili (@irsdl)公布了.NET Remoting应用程序可能存在反序列化安全风险,当服务端使用HTTP信道中的SoapServerF ...