// enums/OzWitch.java
// The witches in the land of Oz
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())
System.out.println(
witch + ": " + witch.getDescription());
}
}
输出为:
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 类型来修饰一个 enum 实例,但是在 case 语句中却不必如此。下面的例子使用 enum 构造了一个小型状态机:
// enums/TrafficLight.java
// Enums in switch statements
// Define an enum type:
enum Signal { GREEN, YELLOW, RED, }
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch(color) {
// Note 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++) {
System.out.println(t);
t.change();
}
}
}
输出为:
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
编译器并没有抱怨 switch 中没有 default 语句,但这并不是因为每一个 Signal 都有对应的 case 语句。如果你注释掉其中的某个 case 语句,编译器同样不会抱怨什么。这意味着,你必须确保自己覆盖了所有的分支。但是,如果在 case 语句中调用 return,那么编译器就会抱怨缺少 default 语句了。这与是否覆盖了 enum 的所有实例无关。
// enums/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);
}
}
输出为:
HITHER
YON
因为 getEnumConstants() 是 Class 上的方法,所以你甚至可以对不是枚举的类调用此方法:
// enums/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("Expected: " + e);
}
}
}
// onjava/Enums.java
package onjava;
import java.util.*;
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)];
}
}
Compiled from "NotClasses.java"
abstract class LikeClasses extends
java.lang.Enum<LikeClasses> {
public static final LikeClasses WINKEN;
public static final LikeClasses BLINKEN;
public static final LikeClasses NOD;
public static LikeClasses[] values();
Code:
0: getstatic #2 // Field
$VALUES:[LLikeClasses;
3: invokevirtual #3 // Method
"[LLikeClasses;".clone:()Ljava/lang/Object;
...
在方法 f1() 中,编译器不允许我们将一个 enum 实例当作 class 类型。如果我们分析一下编译器生成的代码,就知道这种行为也是很正常的。因为每个 enum 元素都是一个 LikeClasses 类型的 static final 实例。
// enums/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 {
@Override
public int amount() { // Disallow
throw new RuntimeException("ABORT.amount()");
}
},
STOP { // This must be the last instance.
@Override
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)];
}
}
// enums/VendingMachine.java
// {java VendingMachine VendingMachineInput.txt}
import java.util.*;
import java.io.IOException;
import java.util.function.*;
import java.nio.file.*;
import java.util.stream.*;
enum Category {
MONEY(Input.NICKEL, Input.DIME,
Input.QUARTER, Input.DOLLAR),
ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS,
Input.SODA, Input.SOAP),
QUIT_TRANSACTION(Input.ABORT_TRANSACTION),
SHUT_DOWN(Input.STOP);
private Input[] values;
Category(Input... types) { values = types; }
private static EnumMap<Input,Category> categories =
new EnumMap<>(Input.class);
static {
for(Category c : Category.class.getEnumConstants())
for(Input type : c.values)
categories.put(type, c);
}
public static Category categorize(Input input) {
return categories.get(input);
}
}
public class VendingMachine {
private static State state = State.RESTING;
private static int amount = 0;
private static Input selection = null;
enum StateDuration { TRANSIENT } // Tagging enum
enum State {
RESTING {
@Override
void next(Input input) {
switch(Category.categorize(input)) {
case MONEY:
amount += input.amount();
state = ADDING_MONEY;
break;
case SHUT_DOWN:
state = TERMINAL;
default:
}
}
},
ADDING_MONEY {
@Override
void next(Input input) {
switch(Category.categorize(input)) {
case MONEY:
amount += input.amount();
break;
case ITEM_SELECTION:
selection = input;
if(amount < selection.amount())
System.out.println(
"Insufficient money for " + selection);
else state = DISPENSING;
break;
case QUIT_TRANSACTION:
state = GIVING_CHANGE;
break;
case SHUT_DOWN:
state = TERMINAL;
default:
}
}
},
DISPENSING(StateDuration.TRANSIENT) {
@Override
void next() {
System.out.println("here is your " + selection);
amount -= selection.amount();
state = GIVING_CHANGE;
}
},
GIVING_CHANGE(StateDuration.TRANSIENT) {
@Override
void next() {
if(amount > 0) {
System.out.println("Your change: " + amount);
amount = 0;
}
state = RESTING;
}
},
TERMINAL {@Override
void output() { System.out.println("Halted"); } };
private boolean isTransient = false;
State() {}
State(StateDuration trans) { isTransient = true; }
void next(Input input) {
throw new RuntimeException("Only call " +
"next(Input input) for non-transient states");
}
void next() {
throw new RuntimeException(
"Only call next() for " +
"StateDuration.TRANSIENT states");
}
void output() { System.out.println(amount); }
}
static void run(Supplier<Input> gen) {
while(state != State.TERMINAL) {
state.next(gen.get());
while(state.isTransient)
state.next();
state.output();
}
}
public static void main(String[] args) {
Supplier<Input> gen = new RandomInputSupplier();
if(args.length == 1)
gen = new FileInputSupplier(args[0]);
run(gen);
}
}
// For a basic sanity check:
class RandomInputSupplier implements Supplier<Input> {
@Override
public Input get() {
return Input.randomSelection();
}
}
// Create Inputs from a file of ';'-separated strings:
class FileInputSupplier implements Supplier<Input> {
private Iterator<String> input;
FileInputSupplier(String fileName) {
try {
input = Files.lines(Paths.get(fileName))
.skip(1) // Skip the comment line
.flatMap(s -> Arrays.stream(s.split(";")))
.map(String::trim)
.collect(Collectors.toList())
.iterator();
} catch(IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Input get() {
if(!input.hasNext())
return null;
return Enum.valueOf(Input.class, input.next().trim());
}
}
输出为:
25
50
75
here is your CHIPS
0
100
200
here is your TOOTHPASTE
0
25
35
Your change: 35
0
25
35
Insufficient money for SODA
35
60
70
75
Insufficient money for SODA
75
Your change: 75
0
Halted
这种设计有一个缺陷,它要求 enum State 实例访问的 VendingMachine 属性必须声明为 static,这意味着,你只能有一个 VendingMachine 实例。不过如果我们思考一下实际的(嵌入式 Java)应用,这也许并不是一个大问题,因为在一台机器上,我们可能只有一个应用程序。
多路分发
当你要处理多种交互类型时,程序可能会变得相当杂乱。举例来说,如果一个系统要分析和执行数学表达式。我们可能会声明 Number.plus(Number),Number.multiple(Number) 等等,其中 Number 是各种数字对象的超类。然而,当你声明 a.plus(b) 时,你并不知道 a 或 b 的确切类型,那你如何能让它们正确地交互呢?
// enums/Outcome.java
package enums;
public enum Outcome { WIN, LOSE, DRAW }
// enums/RoShamBo1.java
// Demonstration of multiple dispatching
// {java enums.RoShamBo1}
package enums;
import java.util.*;
import static enums.Outcome.*;
interface Item {
Outcome compete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}
class Paper implements Item {
@Override
public Outcome compete(Item it) {
return it.eval(this);
}
@Override
public Outcome eval(Paper p) { return DRAW; }
@Override
public Outcome eval(Scissors s) { return WIN; }
@Override
public Outcome eval(Rock r) { return LOSE; }
@Override
public String toString() { return "Paper"; }
}
class Scissors implements Item {
@Override
public Outcome compete(Item it) {
return it.eval(this);
}
@Override
public Outcome eval(Paper p) { return LOSE; }
@Override
public Outcome eval(Scissors s) { return DRAW; }
@Override
public Outcome eval(Rock r) { return WIN; }
@Override
public String toString() { return "Scissors"; }
}
class Rock implements Item {
@Override
public Outcome compete(Item it) {
return it.eval(this);
}
@Override
public Outcome eval(Paper p) { return WIN; }
@Override
public Outcome eval(Scissors s) { return LOSE; }
@Override
public Outcome eval(Rock r) { return DRAW; }
@Override
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());
}
}
输出为:
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
Item 是这几种类型的接口,将会被用作多路分发。RoShamBo1.match() 有两个 Item 参数,通过调用 Item.compete90) 方法开始两路分发。要判定 a 的类型,分发机制会在 a 的实际类型的 compete(内部起到分发的作用。compete() 方法通过调用 eval() 来为另一个类型实现第二次分法。
// enums/RoShamBo2.java
// Switching one enum on another
// {java enums.RoShamBo2}
package enums;
import static enums.Outcome.*;
public enum RoShamBo2 implements Competitor<RoShamBo2> {
PAPER(DRAW, LOSE, WIN),
SCISSORS(WIN, DRAW, LOSE),
ROCK(LOSE, WIN, DRAW);
private Outcome vPAPER, vSCISSORS, vROCK;
RoShamBo2(Outcome paper,
Outcome scissors, Outcome rock) {
this.vPAPER = paper;
this.vSCISSORS = scissors;
this.vROCK = rock;
}
@Override
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);
}
}
输出为:
ROCK vs. ROCK: DRAW
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
PAPER vs. SCISSORS: LOSE
PAPER vs. PAPER: DRAW
PAPER vs. SCISSORS: LOSE
ROCK vs. SCISSORS: WIN
SCISSORS vs. SCISSORS: DRAW
ROCK vs. SCISSORS: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
ROCK vs. PAPER: LOSE
ROCK vs. SCISSORS: WIN
SCISSORS vs. ROCK: LOSE
PAPER vs. SCISSORS: LOSE
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
// enums/Competitor.java
// Switching one enum on another
package enums;
public interface Competitor<T extends Competitor<T>> {
Outcome compete(T competitor);
}
// enums/RoShamBo.java
// Common tools for RoShamBo examples
package enums;
import onjava.*;
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));
}
}
play() 方法没有将类型参数 T 作为返回值类型,因此,似乎我们应该在 Class<T> 中使用通配符来代替上面的参数声明。然而,通配符不能扩展多个基类,所以我们必须采用以上的表达式。
// enums/RoShamBo3.java
// Using constant-specific methods
// {java enums.RoShamBo3}
package enums;
import static enums.Outcome.*;
public enum RoShamBo3 implements Competitor<RoShamBo3> {
PAPER {
@Override
public Outcome compete(RoShamBo3 it) {
switch(it) {
default: // To placate the compiler
case PAPER: return DRAW;
case SCISSORS: return LOSE;
case ROCK: return WIN;
}
}
},
SCISSORS {
@Override
public Outcome compete(RoShamBo3 it) {
switch(it) {
default:
case PAPER: return WIN;
case SCISSORS: return DRAW;
case ROCK: return LOSE;
}
}
},
ROCK {
@Override
public Outcome compete(RoShamBo3 it) {
switch(it) {
default:
case PAPER: return LOSE;
case SCISSORS: return WIN;
case ROCK: return DRAW;
}
}
};
@Override
public abstract Outcome compete(RoShamBo3 it);
public static void main(String[] args) {
RoShamBo.play(RoShamBo3.class, 20);
}
}
输出为:
ROCK vs. ROCK: DRAW
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
PAPER vs. SCISSORS: LOSE
PAPER vs. PAPER: DRAW
PAPER vs. SCISSORS: LOSE
ROCK vs. SCISSORS: WIN
SCISSORS vs. SCISSORS: DRAW
ROCK vs. SCISSORS: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
ROCK vs. PAPER: LOSE
ROCK vs. SCISSORS: WIN
SCISSORS vs. ROCK: LOSE
PAPER vs. SCISSORS: LOSE
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
PAPER vs. PAPER: DRAW
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
ROCK vs. SCISSORS: WIN
ROCK vs. ROCK: DRAW
ROCK vs. SCISSORS: WIN
PAPER vs. SCISSORS: LOSE
SCISSORS vs. SCISSORS: DRAW
PAPER vs. SCISSORS: LOSE
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
PAPER vs. ROCK: WIN
PAPER vs. SCISSORS: LOSE
SCISSORS vs. PAPER: WIN
ROCK vs. SCISSORS: WIN
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
ROCK vs. ROCK: DRAW
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
PAPER vs. SCISSORS: LOSE
PAPER vs. PAPER: DRAW
PAPER vs. SCISSORS: LOSE
ROCK vs. SCISSORS: WIN
SCISSORS vs. SCISSORS: DRAW
ROCK vs. SCISSORS: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
ROCK vs. PAPER: LOSE
ROCK vs. SCISSORS: WIN
SCISSORS vs. ROCK: LOSE
PAPER vs. SCISSORS: LOSE
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
We can simplify the solution even more by noting that each enum instance has a fixed
value (based on its declaration order) and that ordinal() produces this value. A two-
dimensional array mapping the competitors onto the outcomes produces the smallest
and most straightforward solution (and possibly the fastest, although remember that
EnumMap uses an internal array):
// enums/RoShamBo6.java
// Enums using "tables" instead of multiple dispatch
// {java enums.RoShamBo6}
package enums;
import static enums.Outcome.*;
enum RoShamBo6 implements Competitor<RoShamBo6> {
PAPER, SCISSORS, ROCK;
private static Outcome[][] table = {
{ DRAW, LOSE, WIN }, // PAPER
{ WIN, DRAW, LOSE }, // SCISSORS
{ LOSE, WIN, DRAW }, // ROCK
};
@Override
public Outcome compete(RoShamBo6 other) {
return table[this.ordinal()][other.ordinal()];
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo6.class, 20);
}
}
输出为:
ROCK vs. ROCK: DRAW
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK: LOSE
PAPER vs. SCISSORS: LOSE
PAPER vs. PAPER: DRAW
PAPER vs. SCISSORS: LOSE
ROCK vs. SCISSORS: WIN
SCISSORS vs. SCISSORS: DRAW
ROCK vs. SCISSORS: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
ROCK vs. PAPER: LOSE
ROCK vs. SCISSORS: WIN
SCISSORS vs. ROCK: LOSE
PAPER vs. SCISSORS: LOSE
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN