1.1 枚举类型的基本特性
- values() 遍历枚举,顺序和枚举定义的顺序一致
- ordinal() 获取一个int,表示声明顺序,可以比较枚举的大小
- 可以使用 == 、equals比较枚举
- Enum.valueOf 静态方法可以从字符串获取枚举实例
- enum声明的枚举,都是继承自java.lang.Enum类
java
public class EnumDemo2 {
public enum Shrubbery {
GROUND,
CRAWLING,
BRANCHED;
}
public static void main(String[] args) {
for (Shrubbery s : Shrubbery.values()) {
System.out.println(s + " ordinal: " + s.ordinal());
System.out.println(s.compareTo(Shrubbery.CRAWLING) + " ");
System.out.println(s.equals(Shrubbery.CRAWLING) + " ");
System.out.println(s == Shrubbery.CRAWLING);
System.out.println(s.getDeclaringClass());
System.out.println(s.name());
System.out.println("***********");
}
for (String s : "GROUND CRAWLING BRANCHED".split(" ")) {
Shrubbery shrubbery = Enum.valueOf(Shrubbery.class, s);
System.out.println(shrubbery);
}
}
}
输出结果
kotlin
GROUND ordinal: 0
-1
false
false
class com.edfeff.ch01.EnumDemo2$Shrubbery
GROUND
***********
CRAWLING ordinal: 1
0
true
true
class com.edfeff.ch01.EnumDemo2$Shrubbery
CRAWLING
***********
BRANCHED ordinal: 2
1
false
false
class com.edfeff.ch01.EnumDemo2$Shrubbery
BRANCHED
***********
GROUND
CRAWLING
BRANCHED
静态导入枚举类型
arduino
public enum SpicinessEnum {
NOT, MILD, MEDIUM, HOT, FLAMING
}
typescript
//静态引入枚举声明
import static com.edfeff.ch01.SpicinessEnum.*;
public class Burrito2 {
SpicinessEnum degree;
public Burrito2(SpicinessEnum degree) {
this.degree = degree;
}
@Override
public String toString() {
return "Burrito is " + degree;
}
public static void main(String[] args) {
//直接使用枚举
System.out.println(new Burrito2(NOT));
System.out.println(new Burrito2(MEDIUM));
System.out.println(new Burrito2(HOT));
}
}
1.2 在枚举类型中增加自定义方法
枚举类不能被继承,但是可以定义方法。
- 实例必须在方法之前定义
- 构造器访问权限只能是包或者private
- 枚举定义完成后,不能创建新的类型
java
public enum OzWitch {
// 实例必须在方法之前定义
WEST("Wicked Witch of the West"),
NORTH("Good North Witch"),
EAST("Evil East Witch");
private String description;
// 构造器的访问权限必须是包级或private
OzWitch(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static void main(String[] args) {
for (OzWitch w : OzWitch.values()) {
System.out.printf("%s: %s\n", w, w.getDescription());
}
//WEST: Wicked Witch of the West
//NORTH: Good North Witch
//EAST: Evil East Witch
}
}
重载枚举类型中的方法
- 重载toString
java
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) {
Stream.of(values()).forEach(System.out::println);
//Scout
//Cargo
//Transport
//Cruiser
//Battleship
//Mothership
}
}
1.3 在switch语句中使用枚举
switch可以使用整型、字符串和enum。因为枚举内部构建了一个整型序列,可以通过ordinal()获取
- switch 中case需要覆盖所有的枚举,否则就必须加入default分支,不然就会编译出错
java
enum Signal {
RED, GREEN, YELLOW
}
public class TrafficLight {
private Signal color = Signal.RED;
public void change() {
switch (color) {
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 < 4; 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
*/
}
}
1.4 values()方法的神秘之处
java的枚举类型都是编译器通过继承Enum类来创建,但Enum类没有values()方法。values()方法在哪里?
- values()是编译器添加的静态方法
- valueOf()方法也添加到枚举类中了
- 枚举类被final修饰,不能被继承
java
public enum Explore {
HERE, THERE
}
java
public class Reflection {
public static Set<String> analyze(Class<?> enumClass) {
System.out.println("----- Analyzing " + enumClass + "-----");
System.out.println("Interfaces:");
for (Type t : enumClass.getGenericInterfaces()) {
System.out.println(t);
}
System.out.println("Base: " + enumClass.getSuperclass());
System.out.println("Methods:");
Set<String> methods = new TreeSet<>();
for (Method m : enumClass.getMethods()) {
methods.add(m.getName());
}
System.out.println(methods);
return methods;
}
public static void main(String[] args) {
Set<String> exploreMethods = analyze(Explore.class);
Set<String> enumMethods = analyze(Enum.class);
System.out.println("Explore.containsAll(Enum)? " + exploreMethods.containsAll(enumMethods));
System.out.println("Explore.removeAll(Enum)? " + exploreMethods.removeAll(enumMethods));
System.out.println(exploreMethods);
/*
----- Analyzing class com.edfeff.ch01.Explore-----
Interfaces:
Base: class java.lang.Enum
Methods:
[compareTo, describeConstable, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]
----- Analyzing class java.lang.Enum-----
Interfaces:
interface java.lang.constant.Constable
java.lang.Comparable<E>
interface java.io.Serializable
Base: class java.lang.Object
Methods:
[compareTo, describeConstable, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait]
Explore.containsAll(Enum)? true
Explore.removeAll(Enum)? true
[values]
*/
}
}
反编译 javap Explore.class
java
public final class com.edfeff.ch01.Explore extends java.lang.Enum<com.edfeff.ch01.Explore> {
public static final com.edfeff.ch01.Explore HERE;
public static final com.edfeff.ch01.Explore THERE;
public static com.edfeff.ch01.Explore[] values();
public static com.edfeff.ch01.Explore valueOf(java.lang.String);
static {};
}
values()是编译器插入的静态方法,所以枚举类型向上转型Enum时,就没有values方法,此时获取所有枚举,只有通过Class的getEnumConstants()方法了
arduino
public enum Search {
HITHER, YON;
}
typescript
public class UpcastEnum {
public static void main(String[] args) {
Search[] vals = Search.values();
//e向上转型成Enum
Enum e = Search.HITHER;
// e.values();//错误,Enum没有values方法
for (Enum enumConstant : e.getClass().getEnumConstants()) {
System.out.println(enumConstant);
}
/*
HITHER
YON
*/
}
}
普通类也可以调用getEnumConstants()方法,只是返回值是null
vbnet
public class NonEnum {
public static void main(String[] args) {
Class<Integer> integerClass = Integer.class;
try {
for (Integer enumConstant : integerClass.getEnumConstants()) {
System.out.println(enumConstant);
}
} catch (Exception e) {
System.out.println("Excepted: " + e);
//Excepted: java.lang.NullPointerException: Cannot read the array length because "<local2>" is null
}
}
}
1.5 实现而不是继承
枚举对象都继承自Enum,所以不能继承其他类,但是可以实现接口
java
enum CartoonCharacter implements Supplier<CartoonCharacter> {
SLAPPY, SPANKY, PUNCHY, SOUP,
SCAREDY, SKELETON, NERVOUS;
@Override
public CartoonCharacter get() {
return values()[new Random().nextInt(values().length)];
}
}
public class EnumImplementation {
// 接收Supplier接口实现
public static <T> void printNext(Supplier<T> factory) {
System.out.print(factory.get() + ",");
}
public static void main(String[] args) {
CartoonCharacter cc = CartoonCharacter.SLAPPY;
for (int i = 0; i < 10; i++) {
printNext(cc);
}
// SPANKY,NERVOUS,SCAREDY,SLAPPY,SLAPPY,SPANKY,SOUP,SLAPPY,SPANKY,NERVOUS,
}
}
1.6 随机选择
随机从枚举中获取一个的泛型代码
java
public class Enums {
private static Random random = 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[random.nextInt(values.length)];
}
}
使用示例
java
enum Activity {
SETTING, PLAYING, RESTING,
SLEEPING, WALKING, WORKING;
}
public class RandomTest {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Activity activity = Enums.random(Activity.class);
System.out.print(activity + ",");
}
// RESTING,WORKING,PLAYING,WORKING,PLAYING,WORKING,WALKING,RESTING,SETTING,PLAYING,
}
}
1.7 使用接口来组织枚举
想对元素进行分类,可使用接口对元素进行分组,基于接口生成一个枚举
- 实现接口可以子类化枚举
java
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 static void main(String[] args) {
Food food = Appetizer.SALAD;
food = MainCourse.LASAGNE;
food = Dessert.GELATO;
food = Coffee.CAPPUCCINO;
}
}
处理一组枚举类型时,枚举比接口更好用,于是可以创建"由枚举组成的枚举"
- 每个外部枚举都接收相应的Class,然后可使用getEnumConstants()访问所有枚举实例
- 创建一个由枚举类型组成的枚举的意义在于,可以方便的遍历每一个Course
java
public enum Course {
APPETIZER(Food.Appetizer.class),
MAINCOURSE(Food.MainCourse.class),
DESSERT(Food.Dessert.class),
COFFEE(Food.Coffee.class);
private Food[] values;
Course(Class<? extends Food> kind) {
values = kind.getEnumConstants();
}
public Food randomSelection() {
return Enums.random(values);
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
for (Course course : Course.values()) {
Food food = course.randomSelection();
System.out.println(food);
}
System.out.println("---");
}
/*
SPRING_ROLLS
VINDALOO
FRUIT
DECAF_COFFEE
---
SOUP
VINDALOO
FRUIT
TEA
---
* */
}
}
在枚举内嵌套枚举,写法更加简洁
csharp
public enum SecurityCategory {
STOCK(Security.Stock.class),
BOND(Security.Bond.class);
private Security[] values;
SecurityCategory(Class<? extends Security> kind) {
values = kind.getEnumConstants();
}
public Security randomSelection() {
return Enums.random(values);
}
/*枚举内嵌枚举*/
interface Security {
enum Stock implements Security {
SHORT, LONG, MARGIN;
}
enum Bond implements Security {
MUNICIPAL, JUNK;
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
SecurityCategory securityCategory = Enums.random(SecurityCategory.class);
System.out.print(securityCategory.randomSelection() + ",");
}
}
}
1.8 用EnumSet来代替标识
EnumSet用来配合Enum使用,内部使用long变量做位数组,一个枚举实例占用1位,非常高效,性能很好。
- 枚举元素少于64时,内部使用long存储
- 大于64时,使用long数组存储
java
import java.util.EnumSet;
public class EnumSets {
public static void main(String[] args) {
// 创建一个枚举集合
EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);
//添加一个枚举
points.add(AlarmPoints.BATHROOM);
System.out.println(points);
//添加多个枚举
points.addAll(EnumSet.of(AlarmPoints.STAIR1, AlarmPoints.STAIR2, AlarmPoints.LOBBY));
// 创建一个枚举集合
points = EnumSet.allOf(AlarmPoints.class);
//移除枚举
points.remove(AlarmPoints.STAIR1);
points.remove(AlarmPoints.STAIR2);
// 移除多个枚举
points.removeAll(EnumSet.of(AlarmPoints.LOBBY, AlarmPoints.KITCHEN));
System.out.println(points);
// 获取枚举集合的补集
points = EnumSet.complementOf(points);
System.out.println(points);
//遍历枚举
EnumSet<AlarmPoints> alarmPoints = EnumSet.allOf(AlarmPoints.class);
for (AlarmPoints alarmPoint : alarmPoints) {
System.out.print(alarmPoint.name() + ",");
}
}
}
1.9 使用 EnumMap
EnumMap是一个Map,所有的键都来自于一个枚举类型,内部采用数组实现,性能好。
- 只能使用枚举的元素作为键
- EnumMap中元素的顺序由枚举中定义的顺序决定
- EnumMap的值对象可以改变,运行时可以修改
命令模式示例
java
interface Command {
void action();
}
public class EnumMaps {
public static void main(String[] args) {
EnumMap<AlarmPoints, Command> em = new EnumMap<>(AlarmPoints.class);
em.put(AlarmPoints.KITCHEN, () -> System.out.println("Kitchen fire!"));
em.put(AlarmPoints.BATHROOM, () -> System.out.println("Bathroom fire!"));
for (Map.Entry<AlarmPoints, Command> entry : em.entrySet()) {
System.out.print(entry.getKey() + ": ");
entry.getValue().action();
}
}
}
1.10 常量特定方法
枚举类的表驱动模式
- 为每个枚举类编写不同的方法实现
java
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 value : values()) {
System.out.println(value.getInfo());
}
}
}
使用EnumSet来处理常量特定方法
- 枚举类实例的添加顺序不重要,遍历时都会按照定义的顺序执行
java
public class CarWash {
public enum Cycle {
UNDERBODY {
@Override
void action() {
System.out.println("Cleaning underbody");
}
},
WINDSHIELD {
@Override
void action() {
System.out.println("Washing windshield");
}
},
LIGHTS {
@Override
void action() {
System.out.println("Cleaning lights");
}
},
;
abstract void action();
}
EnumSet<Cycle> cycles = EnumSet.of(Cycle.LIGHTS, Cycle.WINDSHIELD);
public void addCycle(Cycle cycle) {
cycles.add(cycle);
}
public void washCar() {
for (Cycle cycle : cycles) {
cycle.action();
}
}
@Override
public String toString() {
return cycles.toString();
}
public static void main(String[] args) {
CarWash wash = new CarWash();
System.out.println(wash);
wash.washCar();
wash.addCycle(Cycle.UNDERBODY);
wash.addCycle(Cycle.UNDERBODY);
System.out.println(wash);
wash.washCar();
}
}
枚举可以重写方法
java
public enum OverrideConstantSpecific {
NUT, BOLT, WASHER {
void f() {
System.out.println("nut,bolt,washer");
}
};
void f() {
System.out.println("default");
}
public static void main(String[] args) {
for (OverrideConstantSpecific c : values()) {
System.out.println(c + ": ");
c.f();
}
/*
NUT:
default
BOLT:
default
WASHER:
nut,bolt,washer
*/
}
}
1.10.1 用枚举实现职责链模式
创建一批用于解决目标问题的不同方法,组成一条链子,请求到达时,顺着执行下去,直到遇到可以处理该请求的方法
java
package org.example.ch01;
import java.util.Iterator;
class Mail {
enum GeneralDelivery {
YES, NO1, NO2, NO3, NO4, NO5;
}
enum Scannability {
UNSCANNABLE, YES1, YES2, YES3, YES4;
}
enum Readability {
ILLEGIBLE, YES1, YES2, YES3, YES4;
}
GeneralDelivery generalDelivery;
Scannability scannability;
Readability readability;
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;
}
public static Mail randomMail() {
Mail mail = new Mail();
mail.generalDelivery = Enums.random(Mail.GeneralDelivery.class);
mail.scannability = Enums.random(Mail.Scannability.class);
mail.readability = Enums.random(Mail.Readability.class);
return mail;
}
public static Iterable<Mail> generator(final int count) {
return new Iterable<Mail>() {
int n = count;
@Override
public Iterator<Mail> iterator() {
return new Iterator<Mail>() {
@Override
public boolean hasNext() {
return n-- > 0;
}
@Override
public Mail next() {
return randomMail();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}
public class PostOffice {
// 职责链模式体现在这里
//每一个处理器 只处理特定类型的邮件
// 枚举的顺序决定了被应用的顺序,适合不需要动态调整顺序的业务
// mail会按照顺序依次尝试,直到可以处理
enum MailHandler {
GENERAL_DELIVERY {
@Override
boolean handle(Mail mail) {
switch (mail.generalDelivery) {
case YES:
System.out.println(mail + " is a general delivery");
return true;
default:
return false;
}
}
},
MACHINE_SCAN {
@Override
boolean handle(Mail mail) {
switch (mail.scannability) {
case UNSCANNABLE:
return true;
default:
return false;
}
}
},
//... 省略
;
abstract boolean handle(Mail mail);
}
//处理的入口方法
static void handle(Mail mail) {
//把mail交给所有的处理器依次处理
for (MailHandler handler : MailHandler.values()) {
if (handler.handle(mail)) {
return;
}
System.out.println(mail + " is a dead letter.");
}
}
public static void main(String[] args) {
for (Mail mail : Mail.generator(3)) {
System.out.println(mail.details());
handle(mail);
System.out.println("*****");
}
}
}
1.10.2 用枚举实现状态机
自动售货机状态
java
//自动售货机输入状态
public enum Input {
//货币
NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),
//商品
TOOTHPASTE(200), COKE(350), CHIPS(75), SODA(100),
//停止状态
ABORT_TRANSACTION {
@Override
int amount() {
throw new RuntimeException("ABORT_TRANSACTION");
}
},
STOP {
@Override
int amount() {
throw new RuntimeException("STOP");
}
};
private int value;
Input(int value) {
this.value = value;
}
Input() {
}
int amount() {
return value;
}
static Random rand = new Random(47);
public static Input randomSelection() {
return values()[rand.nextInt(values().length - 1)];
}
}
java
//种类 枚举分组
enum Category {
// 货币
MONEY(Input.NICKEL, Input.DIME, Input.QUARTER, Input.DOLLAR),
// 货物
ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS, Input.SODA),
// 退出
QUIT_TRANSACTION(Input.ABORT_TRANSACTION),
// 停止
SHUT_DOWN(Input.STOP);
private Input[] values;
Category(Input... values) {
this.values = values;
}
//输入状态和分类的映射
private static EnumMap<Input, Category> categories = new EnumMap<>(Input.class);
static {
//创建分类初始化映射
for (Category c : Category.class.getEnumConstants()) {
for (Input value : c.values) {
categories.put(value, 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}
enum State {
RESTING {
@Override
void next(Input input) {
switch (Category.categorize(input)) {
case MONEY:
amount += input.amount();
state = State.ADDING_MONEY;
break;
case SHUT_DOWN:
state = State.TERMINAL;
break;
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("Not enough money for " + input);
} else {
state = State.DISPENSING;
}
break;
case QUIT_TRANSACTION:
state = State.GIVING_CHANGE;
break;
case SHUT_DOWN:
state = State.TERMINAL;
break;
default:
}
}
},
DISPENSING(StateDuration.TRANSIENT) {
@Override
void next() {
System.out.println("Dispensing " + selection);
amount -= selection.amount();
state = State.GIVING_CHANGE;
}
},
GIVING_CHANGE(StateDuration.TRANSIENT) {
@Override
void next() {
if (amount > 0) {
System.out.println("Your change: " + amount);
amount = 0;
}
state = State.RESTING;
}
},
TERMINAL {
@Override
void output() {
System.out.println("Halted");
}
};
private boolean isTransient = false;
State() {
}
State(StateDuration stateDuration) {
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 non-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) {
VendingMachine.State.run(new RandomInputSupplier());
}
}
class RandomInputSupplier implements Supplier<Input> {
public Input get() {
return Input.randomSelection();
}
}
1.11 多路分发
处理多个交互类型时,程序会变得混乱,使用多路分发处理
- 多路分发,保持了语法的优雅
猜拳的例子
typescript
public enum Outcome {
WIN, LOSE, DRAW
}
interface Item {
Outcome compete(Item item);
Outcome eval(Paper paper);
Outcome eval(Scissors scissors);
Outcome eval(Rock rock);
}
class Paper implements Item {
@Override
public String toString() {
return "Paper";
}
@Override
public Outcome compete(Item item) {
return item.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;
}
}
class Scissors implements Item {
@Override
public String toString() {
return "Scissors";
}
@Override
public Outcome compete(Item item) {
return item.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;
}
}
class Rock implements Item {
@Override
public String toString() {
return "Rock";
}
@Override
public Outcome compete(Item item) {
return item.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;
}
}
arduino
public class RoShamBo1 {
static final int size = 20;
private static Random random = new Random(47);
private static Item newItem() {
switch (random.nextInt(3)) {
default:
case 0:
return new Paper();
case 1:
return new Scissors();
case 2:
return new Rock();
}
}
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 < 20; i++) {
match(newItem(), newItem());
}
}
}
a的类型在运行时确定,第一次可以确定compete的调用,内部对剩余类型调用eval,执行了第二次分发。
- this作为参数传入eval,产生一个重载eval的调用,当第二次分发结束后,就知道两个item的准确类型
1.11.1 使用枚举类分发
枚举实例不是类型,不能重载eval进行分发,可以用查询表的结构实现
csharp
/**
* 竞争接口
* @param <T>
*/
public interface Competitor<T extends Competitor<T>> {
Outcome compete(T competitor);
}
java
/**
* 表驱动策略
*/
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 final Outcome vPaper;
private final Outcome vScissors;
private final Outcome vRock;
//构造函数中,存储与布、剪刀、石头的结果
RoShamBo2(Outcome vPaper, Outcome vScissors, Outcome vRock) {
this.vPaper = vPaper;
this.vScissors = vScissors;
this.vRock = vRock;
}
@Override
public Outcome compete(RoShamBo2 it) {
//根据it的枚举类型,确定返回哪一个表记录
return switch (it) {
//当前与剪刀的比较结果
case SCISSORS -> vScissors;
//当前与石头的比较结果
case ROCK -> vRock;
//当前与布的比较结果
default -> vPaper;
};
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo2.class, 20);
}
}
arduino
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.rand(rsbClass), Enums.rand(rsbClass));
}
}
}
更容易理解的表驱动分发
java
/**
* 表驱动策略
*/
class Tuple<T extends Enum<T>> {
private final T item1;
private final T item2;
public Tuple(T item1, T item2) {
this.item1 = item1;
this.item2 = item2;
}
@Override
public int hashCode() {
return (item1.name() + "vs" + item2.name()).hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Tuple) {
Tuple<T> tuple = (Tuple<T>) obj;
return item1.equals(tuple.item1) && item2.equals(tuple.item2);
}
return false;
}
}
enum Choice {
PAPER, SCISSORS, ROCK
}
public class RoShamBo3 {
static Map<Tuple<Choice>, Outcome> table = new HashMap<>() {
{
put(new Tuple<>(Choice.PAPER, Choice.PAPER), Outcome.DRAW);
put(new Tuple<>(Choice.PAPER, Choice.SCISSORS), Outcome.LOSE);
put(new Tuple<>(Choice.PAPER, Choice.ROCK), Outcome.WIN);
put(new Tuple<>(Choice.SCISSORS, Choice.PAPER), Outcome.WIN);
put(new Tuple<>(Choice.SCISSORS, Choice.SCISSORS), Outcome.DRAW);
put(new Tuple<>(Choice.SCISSORS, Choice.ROCK), Outcome.LOSE);
put(new Tuple<>(Choice.ROCK, Choice.PAPER), Outcome.LOSE);
put(new Tuple<>(Choice.ROCK, Choice.SCISSORS), Outcome.WIN);
put(new Tuple<>(Choice.ROCK, Choice.ROCK), Outcome.DRAW);
}
};
public static void match(Choice a, Choice b) {
System.out.println(a + " vs " + b + ": " + table.get(new Tuple<>(a, b)));
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
match(Enums.rand(Choice.class), Enums.rand(Choice.class));
}
}
}
1.11.2 使用常量特定方法
java
public enum RoShamBo3 implements Competitor<RoShamBo3> {
PAPER {
@Override
public Outcome compete(RoShamBo3 it) {
return switch (it) {
case PAPER -> Outcome.DRAW;
case SCISSORS -> Outcome.WIN;
case ROCK -> Outcome.LOSE;
};
}
},
SCISSORS {
@Override
public Outcome compete(RoShamBo3 it) {
return switch (it) {
case PAPER -> Outcome.WIN;
case SCISSORS -> Outcome.DRAW;
case ROCK -> Outcome.LOSE;
};
}
},
ROCK {
@Override
public Outcome compete(RoShamBo3 competitor) {
return switch (competitor) {
case PAPER -> Outcome.LOSE;
case SCISSORS -> Outcome.WIN;
case ROCK -> Outcome.DRAW;
};
}
};
@Override
public Outcome compete(RoShamBo3 competitor) {
throw new RuntimeException("Not implemented");
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo3.class, 20);
}
}
1.11.3 使用EnumMap分发
EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>>
组成二级Map,实现双路分发
java
import java.util.EnumMap;
public enum RoShamBo5 implements Competitor<RoShamBo5> {
PAPER, SCISSORS, ROCK;
static EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>> table = new EnumMap<>(RoShamBo5.class);
static {
for (RoShamBo5 it : RoShamBo5.values()) {
table.put(it, new EnumMap<>(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(PAPER, vPaper);
row.put(SCISSORS, vScissors);
row.put(ROCK, vRock);
}
public Outcome compete(RoShamBo5 competitor) {
return table.get(this).get(competitor);
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo5.class, 20);
}
}
1.11.4 使用二维数组
直接使用二维数组,最简单易懂的方案
- 适合简单的、不变动的业务情况
typescript
public enum RoShamBo6 implements Competitor<RoShamBo6> {
PAPER, SCISSORS, ROCK;
private static Outcome[][] table = {
{DRAW, LOSE, WIN},//布
{WIN, DRAW, LOSE},//剪刀
{LOSE, WIN, DRAW},//石头
};
@Override
public Outcome compete(RoShamBo6 competitor) {
return table[this.ordinal()][competitor.ordinal()];
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo6.class, 20);
}
}
1.13 新特性:switch中的箭头语法
箭头替换冒号,不需要break了
java
import java.util.stream.IntStream;
public class ArrowInSwitch {
static void colons(int i) {
switch (i) {
case 1:
System.out.println("one");
break;
case 2:
System.out.println("two");
break;
case 3:
System.out.println("three");
break;
default:
System.out.println("default");
}
}
static void arrows(int i) {
switch (i) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("three");
default -> System.out.println("default");
}
}
public static void main(String[] args) {
IntStream.range(0, 4).forEach(ArrowInSwitch::colons);
IntStream.range(0, 4).forEach(ArrowInSwitch::arrows);
}
}
1.14 新特性: switch中的case null
case null 这一特性在jdk21中正式发布,17中只是预览版
- switch中支持case为null的情况,减少固定样式重复编码,简化了书写
- switch支持 case default,null 组合的情况,简化了书写
- default 并不会捕获null的情况,
- switch应该覆盖所有值,但是case null省略也可以编译,是因为需要兼容旧代码
java
public class CaseNull {
static void old(String s) {
if (s == null) {
System.out.println("null");
return;
}
switch (s) {
case "XX" -> System.out.println("XX");
default -> System.out.println("default");
}
}
static void checkNull(String s) {
switch (s) {
case "XX" -> System.out.println("XX");
case null -> System.out.println("null");
default -> System.out.println("default");
}
switch (s) {
case "XX":
System.out.println("XX");
break;
//直接判断null
case null:
System.out.println("null");
break;
default:
System.out.println("default");
}
}
static void defaultOnly(String s) {
switch (s) {
case "XX" -> System.out.println("XX");
default -> System.out.println("default");
}
}
static void combineNullAndDefault(String s) {
switch (s) {
case "XX" -> System.out.println("XX");
//直接判断null 和 默认组合到一起
case null, default -> System.out.println("default");
}
}
static void test(Consumer<String> cs) {
cs.accept("XX");
cs.accept("YY");
try {
cs.accept(null);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] args) {
test(CaseNull::old);
test(CaseNull::checkNull);
test(CaseNull::defaultOnly);
test(CaseNull::combineNullAndDefault);
}
}
1.15 新特性: switch表达式
- 冒号语法配合新关键字yield可以从switch返回值
- 箭头语法直接从switch中返回值
java
public class SwitchExpression {
static int colon(String s) {
return switch (s) {
case "abc":
yield 1;
case "def":
yield 2;
default:
yield 0;
};
}
static int arrow(String s) {
return switch (s) {
case "abc" -> 1;
case "def" -> 2;
default -> 0;
};
}
public static void main(String[] args) {
var arr = new String[]{"abc", "def", "xyz"};
for (String s : arr) {
System.out.println(colon(s));
System.out.println(arrow(s));
}
}
}
枚举与switch的应用
java
public class EnumSwitch {
enum Signal {
GREEN, YELLOW, RED
}
Signal color = Signal.RED;
public void change() {
color = switch (color) {
case RED -> Signal.GREEN;
case GREEN -> Signal.YELLOW;
case YELLOW -> Signal.RED;
};
}
}
1.16 新特性:智能转型
这是JEP394,可以在instanceof后直接声明该类型的模式变量,不再需要强制类型转换
java
public class SmartCasting {
static void dumb(Object x) {
if (x instanceof String) {
String s = (String) x;
System.out.println(s.toUpperCase());
}
}
static void smart(Object x) {
//s 被称为模式变量
if (x instanceof String s) {
System.out.println(s.toUpperCase());
}
}
public static void main(String[] args) {
dumb("hello");
smart("hello");
}
}
模式变量与if作用域有一些神奇的作用域行为
java
public class OddScoping {
static void f(Object o) {
if (!(o instanceof String s)) {
System.out.println("Not String");
} else {
//此作用域依然生效
System.out.println(s.toUpperCase());
}
}
static void f2(Object o) {
if (!(o instanceof String s)) {
System.out.println("Not String");
throw new RuntimeException();//注释了,下面s就会编译出错,这不是一个bug,而是设计如此
}
//上面抛出异常结束的话,else语句块也不需要了
// 此作用域依然生效
System.out.println(s.toUpperCase());
}
public static void main(String[] args) {
f(new Object());
f("Hi there");
f2("Hi there");
f2(new Object());
}
}
1.17 新特性:模式匹配
1.17.1 违反里氏替换原则
模式匹配实现了基于类型的行为,却不要求类型具有相同的接口,或者相同的继承层次结构
- 也是运行时确定类型,不是反射,比反射更正式和结构化
- 如果判断的类型全部有相同的基类,那就遵循了里氏替换原则LSP
java
record XX() {
}
interface Pet {
}
class Dog implements Pet {
}
class Fish implements Pet {
}
public class ObjectMatch {
static String match(Object o) {
return switch (o) {
case Dog d -> "woof";
case Fish f -> "blub";
case Pet p -> "pet";
case XX x -> "xx";
case String s -> "string";
case Integer i -> "int";
case null -> "null";
default -> "?";
};
}
}
1.17.2 守卫
守卫可以进一步细化匹配条件,在类型判断后使用 新关键字 when
- 可以是任何布尔表达式,结果为true则匹配
java
sealed interface Shape {
double area();
}
record Circle(double radius) implements Shape {
@Override
public double area() {
return Math.PI * radius * radius;
}
}
record Rectangle(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
}
public class Shapes {
static void classify(Shape shape) {
System.out.println(switch (shape) {
case Circle c when c.area() < 100.0 -> "Small circle of radius " + c.radius();
case Circle c -> "Large circle of radius " + c.radius();
case Rectangle r when r.width() == r.height() -> "Square with side " + r.width();
case Rectangle r -> "Rectangle with sides " + r.width() + " and " + r.height();
});
}
public static void main(String[] args) {
List.of(
new Circle(5.0),
new Circle(25.0),
new Rectangle(5.0, 5.0),
new Rectangle(12.0, 15.0)
).forEach(Shapes::classify);
}
}
1.17.3 支配性
case按照顺序匹配,上面的case优先匹配
- 如果基类在上,编译报错
csharp
sealed interface Base {
}
record Derived() implements Base {
}
public class Dominance {
static String test(Base base) {
//编译报错 java: 此 case 标签由前一个 case 标签支配
return switch (base) {
case Base b -> "Base";
case Derived d -> "Derived";
};
}
public static void main(String[] args) {
System.out.println(test(new Derived()));
}
}
1.17.4 覆盖范围
模式匹配需要覆盖所有可能的值,如果没有覆盖完全,就会报错
- case null 属于特殊情况,为了兼容性并没有要求覆盖
java
sealed interface Transport {
}
record Bicycle(String id) implements Transport {
}
record Glider(int size) implements Transport {
}
record Surfboard(double weight) implements Transport {
}
//取消注释后 下面的switch报错 未覆盖所有可能的值
//record Skis(int length) implements Transport {
//}
public class SealedPatternMatch {
static String exhaustive(Transport t) {
return switch (t) {
case Bicycle b -> "Bicycle " + b.id();
case Glider g -> "Glider " + g.size();
case Surfboard s -> "Surfboard " + s.weight();
};
}
}