《On Java进阶卷》- 笔记-1-枚举类型

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();
        };
    }
}
相关推荐
Mryan200514 分钟前
✨ 使用 Flask 实现头像文件上传与加载功能
后端·python·flask
程序员是干活的19 分钟前
Java EE前端技术编程脚本语言JavaScript
java·大数据·前端·数据库·人工智能
某个默默无闻奋斗的人25 分钟前
【矩阵专题】Leetcode48.旋转图像(Hot100)
java·算法·leetcode
张同学的IT技术日记29 分钟前
重构 MVC:让经典架构完美适配复杂智能系统的后端业务逻辑层(内附框架示例代码)
c++·后端·重构·架构·mvc·软件开发·工程应用
℡余晖^30 分钟前
每日面试题14:CMS与G1垃圾回收器的区别
java·jvm·算法
南囝coding42 分钟前
Coze 开源了!所有人都可以免费使用了
前端·后端·产品
CDwenhuohuo44 分钟前
滚动提示组件
java·前端·javascript
围巾哥萧尘1 小时前
macOS 终端美化安装指南🧣
后端
wei3872452321 小时前
集训总结2
java·数据库·mysql
GoodTime1 小时前
CodeBuddy IDE深度体验:全球首个产设研一体AI工程师的真实使用报告
前端·后端·架构