第19章 枚举类型
关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。
所有的枚举类都继承自Enum类,枚举类没办法继承
19.1 基本的enum特性
java
public enum Shrubbery {
GROUND, CRAWLING, HANGING,HEHE
}
public class Test {
public static void main(String[] args) {
// 返回enum类的实例数组
Shrubbery[] values = Shrubbery.values();
for (Shrubbery value : values) {
// 获得声明时的次序,从0开始
System.out.println(value+"序数:"+value.ordinal());
// 比较,返回相对位置(-1,0,1,2)
System.out.println(value.compareTo(Shrubbery.CRAWLING));
// 比较两者是否相等,默认重写了equals()和hashcode()
System.out.println(value == Shrubbery.CRAWLING);
// 获得其所属的类
System.out.println(value.getDeclaringClass());
// 返回enum实例声明时的名字
System.out.println(value.name());
System.out.println("-----------------");
}
}
}
/*
GROUND序数:0
-1
false
class com.enumerate.Shrubbery
GROUND
-----------------
CRAWLING序数:1
0
true
class com.enumerate.Shrubbery
CRAWLING
-----------------
HANGING序数:2
1
false
class com.enumerate.Shrubbery
HANGING
-----------------
HEHE序数:3
2
false
class com.enumerate.Shrubbery
HEHE
-----------------
*/
静态导入
java
import static com.enumerate.Shrubbery.*;
public class Test {
public static void main(String[] args) {
f(CRAWLING);
}
public static void f(Shrubbery s) {
}
}
19.2 向enum中添加新方法
enum除了不能继承外,可以看作是一个常规的类。
java
public enum Person {
// 定义枚举实例
// 必须在最前
// 当要定义其他成员时,实例后必须有";"
STUDENT("ding"),TEACHER("fu");
private String name;
// 定义其他成员
private Person(String name){
this.name = name;
}
public String getName() {
return name;
}
}
重写方法
java
public enum SpaceShip {
SCOUT,CARGO,TRANSPORT,CRUISER,BATTLESHIP,MOTHERSHIP;
@Override
public String toString() {
String name = name();
return name.charAt(0)+name.substring(1).toLowerCase();
}
}
public static void main(String[] args) {
for (SpaceShip value : SpaceShip.values()) {
System.out.println(value);
}
}
/*
Scout
Cargo
Transport
Cruiser
Battleship
Mothership
*/
19.3 switch语句中的enum
java
public static void main(String[] args) {
SpaceShip spaceShip = SpaceShip.BATTLESHIP;
// 因为在switch这里已经放置了枚举类型,在case中就不需要指定枚举类型了
switch (spaceShip) {
case CARGO:
System.out.println("1");
break;
case SCOUT:
System.out.println("2");
break;
case CRUISER:
System.out.println(3);
}
}
19.4 values()的神秘之处
java
public class Test {
public static void main(String[] args) {
Set<String> subMethods = f(SpaceShip.class);
Set<String> enumMethods = f(Enum.class);
System.out.println("子包含父方法:"+subMethods.containsAll(enumMethods));
System.out.println("移除所有父方法:"+subMethods.removeAll(enumMethods));
System.out.println(subMethods);
}
public static Set<String> f(Class<?> enumClass){
System.out.println("--分析"+enumClass+"---");
System.out.println("接口:");
for (Type t : enumClass.getGenericInterfaces()) {
System.out.println(t);
}
System.out.println("父类:"+enumClass.getSuperclass());
Set<String> methods = new TreeSet<>();
for (Method method : enumClass.getMethods()) {
methods.add(method.getName());
}
System.out.println("方法:");
System.out.println(methods);
return methods;
}
}
/*
--分析class com.enumerate.SpaceShip---
接口:
父类:class java.lang.Enum
方法:
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]
--分析class java.lang.Enum---
接口:
java.lang.Comparable<E>
interface java.io.Serializable
父类:class java.lang.Object
方法:
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait]
子包含父方法:true
移除所有父方法:true
[values]
*/
分析:
- values()是由编译器添加的static方法;
- 编译器添加了包含一个参数的valueOf()方法;
- 编译器会将枚举类标记为final类型;
java
// 通过Class对象获取所有的枚举实例
// 非枚举类也可以调用,但是只会返回null
public static void main(String[] args) {
Person student = Person.STUDENT;
Person[] persons = student.getClass().getEnumConstants();
for (Person person : persons) {
System.out.println(person);
}
}
19.5 实现,而非继承
java
public enum Shrubbery implements Factory<Shrubbery> {
GROUND, CRAWLING, HANGING,HEHE;
private static Random random = new Random();
// 随机返回一个实例
@Override
public Shrubbery next() {
return values()[random.nextInt(values().length)];
}
}
public class Test {
public static void main(String[] args) {
// 通过枚举上的一个类来调用枚举上的方法
Factory<Shrubbery> factory = Shrubbery.CRAWLING;
for (int i = 0; i < 5; i++) {
System.out.println(printT(factory));
}
}
public static <T>T printT(Factory<T> factory){
return factory.next();
}
}
19.6 随机选取
java
// 获得随机枚举实例的通用工具类
public class Enums {
private static Random rand = new Random();
public static <T extends Enum<T>> T randmEnum(Class<T> enumClass){
return random(enumClass.getEnumConstants());
}
private static <T> T random(T[] values){
return values[rand.nextInt(values.length)];
}
}
19.7 使用接口组织枚举
java
// 使用接口组织枚举,达到将枚举分类的目的
public interface Food {
enum Fruit implements Food{
A_FRUIT,B_FRUIT,C_FRUIT;
}
enum Rice implements Food{
A_RICE,B_RICE,C_RICE;
}
}
创建枚举的枚举
java
public enum Course {
// 以枚举Class作为构造器参数
A_COURSE(Food.Rice.class),B_COURSE(Food.Fruit.class);
private Food[] foods;
Course(Class<? extends Food> aClass) {
foods = aClass.getEnumConstants();
}
public Food getFood(){
return Enums.random(foods);
}
}
public static void main(String[] args) {
Course rice = Course.A_COURSE;
System.out.println(rice.getFood());
}
另一种组织枚举的方式
java
public enum Course {
A_COURSE(Foods.Fruit.class),B_COURSE(Foods.Rice.class);
private Foods[] foods;
Course(Class<? extends Foods> aClass) {
foods = aClass.getEnumConstants();
}
public Foods getFood(){
return Enums.random(foods);
}
// 此种方式与第一种方式区别不大,只是将组织枚举的接口放置在枚举内
interface Foods {
enum Fruit implements Foods {
A_FRUIT,B_FRUIT,C_FRUIT;
}
enum Rice implements Foods {
A_RICE,B_RICE,C_RICE;
}
}
}
19.8 使用EnumSet替代标志
EnumSet非常快,内部(可能)使用一个long值作为比特向量。
java
public static void main(String[] args) {
// 创建一个Set,但不添加元素
EnumSet<Person> enumSet = EnumSet.noneOf(Person.class);
System.out.println(enumSet); // []
enumSet.add(Person.TEACHER);
System.out.println(enumSet); // [TEACHER]
// // 创建一个Set,但添加所有元素
enumSet = EnumSet.allOf(Person.class);
System.out.println(enumSet); // [STUDENT, TEACHER]
// 为了效率考虑,of()有很多重载的方法
enumSet.removeAll(EnumSet.of(Person.STUDENT));
System.out.println(enumSet); // [TEACHER]
// 创建一个Set,包含除了源Set中的所有元素
enumSet = EnumSet.complementOf(enumSet);
System.out.println(enumSet); // [STUDENT]
}
19.9 使用EnumMap
命令设计模式 需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。
java
public interface Command {
void action();
}
public static void main(String[] args) {
EnumMap<Person,Command> map = new EnumMap<>(Person.class);
map.put(Person.STUDENT, new Command() {
@Override
public void action() {
System.out.println("这是学生");
}
});
map.put(Person.TEACHER, new Command() {
@Override
public void action() {
System.out.println("这是老师");
}
});
// 以枚举的顺序排列键值对
for (Map.Entry<Person, Command> entry : map.entrySet()) {
entry.getValue().action();
}
}
19.10 常量相关的方法
enum允许程序员为每个枚举实例编写自己的方法。
java
public enum Person {
TEACHER{
@Override
void info(){
System.out.println("学生");
}
},STUDENT{
@Override
void info() {
System.out.println("老师");
}
};
private String flag;
// 定义抽象方法
// 也可以覆盖非抽象方法
abstract void info();
}
public static void main(String[] args) {
Person.STUDENT.info(); // 老师
}
/*
上述代码和内部类非常类似,但是还是有很大区别的,实例没办法访问枚举的非静态变量,实例不能作为Class类型。
*/
19.10.1 使用enum的职责链
职责链设计模式 以多种不同的方式来解决一个问题,然后将他们链接在一起;当一个请求到来时,遍历整个链,直到链中的某个解决方案处理该请求。
java
// 模拟邮局处理邮件
// 邮件
public class Mail {
// 定义邮件的几处信息
enum A_INFO{YES,NO1,NO2,NO3}
enum B_INFO{YES,NO1,NO2,NO3}
enum C_INFO{YES,NO1,NO2,NO3}
public A_INFO aInfo;
public B_INFO bInfo;
public C_INFO cinfo;
static long counter = 0;
long id = counter++;
@Override
public String toString() {
return "Mail"+id;
}
public String details(){
StringBuilder sb = new StringBuilder();
sb.append("Mail -> A_INFO:").append(aInfo)
.append(" B_INFO:").append(bInfo)
.append(" C_INFO:").append(cinfo);
return sb.toString();
}
// 获得一个随机的邮件
public static Mail randomMail(){
Mail mail = new Mail();
mail.aInfo = Enums.randmEnum(A_INFO.class);
mail.bInfo = Enums.randmEnum(B_INFO.class);
mail.cinfo = Enums.randmEnum(C_INFO.class);
return mail;
}
// 获得一个迭代器
public static Iterable<Mail> generator(final int count){
return new Iterable<Mail>() {
@Override
public Iterator<Mail> iterator() {
return new Iterator<Mail>() {
int n = count;
@Override
public boolean hasNext() {
return (n--) > 0;
}
@Override
public Mail next() {
return randomMail();
}
};
}
};
}
}
// 邮局
public class PostOffice {
// 处理器
enum MailHandler{
A_INFO_HANDLER{
@Override
boolean handle(Mail m) {
switch (m.aInfo){
case YES:
System.out.println("由A处理");
return true;
}
return false;
}
},
B_INFO_HANDLER{
@Override
boolean handle(Mail m) {
switch (m.bInfo){
case YES:
System.out.println("由B处理");
return true;
}
return false;
}
},
C_INFO_HANDLER{
@Override
boolean handle(Mail m) {
switch (m.cinfo){
case YES:
System.out.println("由C处理");
return true;
}
return false;
}
};
abstract boolean handle(Mail m);
}
// 处理邮件
public static void handle(Mail m){
for (MailHandler handler : MailHandler.values()) {
if (handler.handle(m)) {
return;
}
}
System.out.println("邮件没有被处理");
}
}
public static void main(String[] args) {
Iterable<Mail> generator = Mail.generator(5);
for (Mail mail : generator) {
PostOffice.handle(mail);
}
}
/*
邮件没有被处理
由C处理
由B处理
由C处理
由A处理
*/
19.10.2 使用enum的状态机
模拟售货机
java
// 输入
public enum Input {
NICKEL/*5美分*/(5), DIME/*十分钱*/(10), QUARTER/*25美分*/(25), DOLLAR/*美元*/(100),
TOOTHPASTE/*牙膏*/(200), CHIPS/*芯片*/(75), SODA/*苏打水*/(100), SOAP/*肥皂*/(50),
ABORT_TRANSACTION/*中止交易*/ {
@Override
int amout() {
throw new RuntimeException("交易中止了");
}
},
STOP {
@Override
int amout() {
throw new RuntimeException("停止了");
}
};
int value;
Input() {
}
Input(int value) {
this.value = value;
}
int amout() {
return value;
}
private static Random random = new Random();
public static Input random(){
// 不包括STOP
return values()[random.nextInt(values().length-1)];
}
}
// 类别
public enum Category {
// 钱
MONEY(NICKEL,DIME,QUARTER,DOLLAR),
// 项目选择
ITEM_SELECTION(TOOTHPASTE,CHIPS,SODA,SOAP),
// 退出交易
QUIT_TRANSACTION(ABORT_TRANSACTION),
// 关闭售货机
SHUT_DOWN(STOP)
;
Input[] inputs;
Category(Input ... types){
inputs = types;
}
// 输入-类别对应关系
private static EnumMap<Input,Category> categorys = new EnumMap<>(Input.class);
static {
// 遍历所有类别
for (Category category : Category.class.getEnumConstants()) {
// 遍历该类别下的所有输入
for (Input input : category.inputs) {
categorys.put(input,category);
}
}
}
// 获得商品对应的类别
public static Category getCategory(Input input){
return categorys.get(input);
}
}
// 售货机
public class VendingMachine {
// 售货机中的金额
private static int amout;
// 售货机状态
private static State state = State.RESTING;
private static Input selection = null;
enum StateDuration/*状态持续时间*/ {TRANSIENT/*短暂的*/}
enum State {
// 待机状态
RESTING/*休眠*/ {
@Override
void next(Input input) {
switch (Category.getCategory(input)) {
case MONEY:
amout += input.amout();
state = ADDING_MONEY;
break;
case SHUT_DOWN:
state = TERMINAL;
}
}
},
ADDING_MONEY/*加钱*/ {
@Override
void next(Input input) {
switch (Category.getCategory(input)) {
case MONEY:
amout += input.amout();
break;
case ITEM_SELECTION:
selection = input;
if (amout < selection.amout()) {
System.out.println("购买" + selection + "的钱不足");
} else {
state =DISPENSING;
}
break;
case QUIT_TRANSACTION:
state = GIVING_CHANGE;
break;
case SHUT_DOWN:
state = TERMINAL;
}
}
},
DISPENSING/*出货*/(StateDuration.TRANSIENT) {
@Override
void next() {
System.out.println("这是您的" + selection);
amout -= selection.amout();
state =GIVING_CHANGE;
}
},
GIVING_CHANGE/*退款*/(StateDuration.TRANSIENT) {
@Override
void next() {
// 每次只能出售一个商品
if (amout > 0) {
System.out.println("退款:" + amout);
amout = 0;
}
state = State.RESTING;
}
},
// 关闭售货机
TERMINAL/*终止*/ {
@Override
void output() {
System.out.println("已停止");
}
};
public boolean isTransient = false;
State() {
}
State(StateDuration trans) {
isTransient = true;
}
void next() {
throw new RuntimeException("");
}
void next(Input input) {
throw new RuntimeException("");
}
void output() {
System.out.println(amout);
}
}
public static void run(Factory<Input> factory){
while (state != State.TERMINAL){
Input input = factory.next();
state.next(input);
while (state.isTransient){
state.next();
}
state.output();
}
}
}
// 测试
public class Test {
public static void main(String[] args) {
VendingMachine.run(new RandomFactory());
}
static class RandomFactory implements Factory<Input>{
@Override
public Input next() {
return Input.random();
}
}
}
注意:run的运行逻辑
- 一开始,状态处于RESTING待机;
- 状态有两种,一种是需要input的RESTING(待机)和ADDING_MONEY(加钱);另一种是不需要输入的DISPENSING(出货)和GIVING_CHANGE(退款),而这两种都被标记为了瞬时状态,他们不会长期存在;
- 出货和退款出现的可能有两种:出货->退款、退款,由于
while (state.isTransient)
的作用,上述两种情况最会产生的状态一定是待机状态; - 所以next(input)最终都是待机和加钱在执行,而出货和退款都在内层循环中执行。
19.11 多路分发
当处理多种交互类型时,程序可能会变得相当杂乱;
而动态机制只能处理一种类型,所以,需要我们自己来判定其他类型。
一个方法调用决定一个类型,当我们需要决定几种类型时,我们就需要几个方法。
此方法的含义是一层套一层的,如果有三种类型,在需要三个方法,b方法嵌套在a方法内,c方法在b内。
java
// 实现剪刀石头布游戏
public class Test {
static Random random = new Random();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
match(getItem(),getItem());
}
}
static Item getItem() {
int i = random.nextInt(3);
switch (i) {
case 0:
return new Paper();
case 1:
return new Scissors();
default:
return new Rock();
}
}
static void match(Item a,Item b){
System.out.println(a +" VS "+b+" "+a.compete(b));
}
}
enum OutCome {WIN, LOSE, DRAW}
// 是这几种类型的接口,被用作多路分发
interface Item {
OutCome compete(Item it);
// 石头剪刀布有三种类型,所以有三个eval()方法
OutCome eval(Paper paper);
OutCome eval(Scissors scissors);
OutCome eval(Rock rock);
}
class Paper/*布*/ implements Item {
/*
compete的逻辑是相反的逻辑,eval站在参数的角度是赢,那compete则是站在类的角度就是输
*/
@Override
public OutCome compete(Item it) {
return it.eval(this);
}
@Override
public OutCome eval(Paper paper) {
return OutCome.DRAW;
}
@Override
public OutCome eval(Scissors scissors) {
return OutCome.WIN;
}
@Override
public OutCome eval(Rock rock) {
return OutCome.LOSE;
}
@Override
public String toString() {
return "布";
}
}
class Scissors/*剪刀*/ implements Item {
@Override
public OutCome compete(Item it) {
return it.eval(this);
}
@Override
public OutCome eval(Paper paper) {
return OutCome.LOSE;
}
@Override
public OutCome eval(Scissors scissors) {
return OutCome.DRAW;
}
@Override
public OutCome eval(Rock rock) {
return OutCome.WIN;
}
@Override
public String toString() {
return "剪刀";
}
}
class Rock/*石头*/ implements Item {
@Override
public OutCome compete(Item it) {
return it.eval(this);
}
@Override
public OutCome eval(Paper paper) {
return OutCome.WIN;
}
@Override
public OutCome eval(Scissors scissors) {
return OutCome.LOSE;
}
@Override
public OutCome eval(Rock rock) {
return OutCome.DRAW;
}
@Override
public String toString() {
return "石头";
}
}
使用enum分发
java
public interface Competitor <T extends Competitor<T>>{
OutCome compete(T competitor);
}
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){
vPaper = paper;
vScissors = scissors;
vRock = rock;
}
// 实现了比较,it只有这三种类型
// 这里仍然使用了两路分发,第一次在传入参数时,第二次在switch选择块中
@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) {
OutCome compete = RoShamBo2.PAPER.compete(RoShamBo2.ROCK);
System.out.println(compete);
}
使用常量相关方法
java
public enum RoShamBo3 implements Competitor<RoShamBo3>{
// 定义了所有的类型
PAPER{
@Override
public OutCome compete(RoShamBo3 it) {
switch (it){
default:
case PAPER:return OutCome.DRAW;
case SCISSORS:return OutCome.WIN;
case ROCK:return OutCome.LOSE;
}
}
},
SCISSORS{
@Override
public OutCome compete(RoShamBo3 it) {
switch (it){
default:
case PAPER:return OutCome.LOSE;
case SCISSORS:return OutCome.DRAW;
case ROCK:return OutCome.WIN;
}
}
},
ROCK{
@Override
public OutCome compete(RoShamBo3 it) {
switch (it){
default:
case PAPER:return OutCome.WIN;
case SCISSORS:return OutCome.LOSE;
case ROCK:return OutCome.DRAW;
}
}
}
;
// 定义抽象方法
@Override
public abstract OutCome compete(RoShamBo3 it) ;
}
使用EnumMap
java
public enum RoShamBo5 implements Competitor<RoShamBo5> {
// 定义了所有的类型
PAPER,
SCISSORS,
ROCK;
// 使用EnumMap储存所有结果
static EnumMap<RoShamBo5, EnumMap<RoShamBo5, OutCome>> table = new EnumMap<>(RoShamBo5.class);
// 初始化table,和每个实例
static {
for (RoShamBo5 it : RoShamBo5.values()) {
table.put(it, new EnumMap<>(RoShamBo5.class));
}
initRow(PAPER, OutCome.DRAW, OutCome.WIN, OutCome.LOSE);
initRow(SCISSORS, OutCome.LOSE, OutCome.DRAW, OutCome.WIN);
initRow(ROCK, OutCome.WIN, OutCome.LOSE, OutCome.DRAW);
}
// 初始化每个实例的结果
static void initRow(RoShamBo5 it, OutCome paper, OutCome scissors, OutCome rock) {
EnumMap<RoShamBo5, OutCome> map = table.get(it);
map.put(PAPER, paper);
map.put(SCISSORS, scissors);
map.put(ROCK, rock);
}
@Override
public OutCome compete(RoShamBo5 it) {
// 注意:第一步get到该实例,第二部get比较的实例
return table.get(this).get(it);
}
public static void main(String[] args) {
System.out.println(RoShamBo5.PAPER.compete(RoShamBo5.SCISSORS));
}
}
使用二维数组
java
// 可以看到,这应该是最简单的,同时也是效率最高的
public enum RoShamBo6 implements Competitor<RoShamBo6>{
PAPER,
SCISSORS,
ROCK;
private static OutCome[][] table = {
{OutCome.DRAW,OutCome.WIN,OutCome.LOSE},
{OutCome.LOSE,OutCome.DRAW,OutCome.WIN},
{OutCome.WIN,OutCome.LOSE,OutCome.DRAW}
};
@Override
public OutCome compete(RoShamBo6 it) {
return table[this.ordinal()][it.ordinal()];
}
}
19.12 总结
枚举在Java中是一个小功能,它所带来的价值是在某些方面可以优雅的解决某些问题。