Java进阶 1-2 枚举

目录

常量特定方法

职责链模式的枚举实现

状态机模式的枚举实现

多路分发

1、使用枚举类型实现分发

2、使用常量特定方法实现分发

3、使用EnumMap实现分发

4、使用二维数组实现分发


本笔记参考自: 《On Java 中文版》


常量特定方法

        在Java中,我们可以通过为每个枚举实例编写不同的方法,来赋予它们不同的行为。一个方式是在枚举类型中定义抽象方法,并在枚举实例中实现它:

【例子:为枚举实例编写方法】

import java.text.DateFormat;
import java.util.Date;

public enum ConstantSpecificMethod {
    DATE_TIME {
        @Override
        String getInfo() {
            return DateFormat.getDateInstance()
                    .format(new Date());
        }
    },
    CLASSPATH {
        @Override
        String getInfo() {// getenv()方法用于获取指定环境变量的值
            return System.getenv("CLASSPATH");
        }
    },
    VERSION {
        @Override
        String getInfo() { // 获取由输入指定的属性
            return System.getProperty("java.version");
        }
    };

    abstract String getInfo();

    public static void main(String[] args) {
        for (ConstantSpecificMethod csm : values())
            System.out.println(csm.getInfo());
    }
}

        程序执行的结果是:

        在这个例子中,我们通过关联的枚举实例来调用对应的方法,这通常被称为表驱动模式

        这里需要复习一点:在面向对象的编程中,不同的行为是和不同的类相关联的。而上述枚举的每个实例都拥有各自不同的行为,这就表明每个实例都相当于不同的类型(体现了多态)。它们的共同点在于它们的基类ConstantSpecificMethod

        但这并不意味着枚举实例能够等价于普通的类:

【例子:枚举实例和类之间的不同点】

enum LikeClasses {
    A {
        @Override
        void behavior() {
            System.out.println("A");
        }
    },
    B {
        @Override
        void behavior() {
            System.out.println("B");
        }
    },
    C {
        @Override
        void behavior() {
            System.out.println("C");
        }
    };

    abstract void behavior();
}

public class NotClasses {
//    void f1(LikeClasses.A instance) {
//    } // 不可行的操作
}

        编译器并不允许f1()的操作:将枚举实例作为类的类型进行使用。为了解释这一点,可以对程序进行反编译javap -c LikeClasses

反编译告诉我们,每个枚举元素都是LikeClasses的一个static final实例。实例无法作为类型进行使用。

        除此之外,不同于内部类,因为枚举实例是静态的,所以我们无法通过外部类LikeClasses直接访问其内部的枚举实例(具体而言,我们无法在非静态域中访问外部类的静态成员)。

(与匿名内部类相比,常量特定方法要显得更加简洁。)

        除此之外,常量特定方法也支持重写

【例子:重写常量特定方法】

public enum OverrideConstantSpecific {
    NUT, BOLT,
    WASHER {
        @Override
        public void f() {
            System.out.println("重写后的f()方法");
        }
    };

    void f() {
        System.out.println("默认的f()方法");
    }

    public static void main(String[] args) {
        for (OverrideConstantSpecific ocs : values()) {
            System.out.print(ocs + ": ");
            ocs.f();
        }
    }
}

        程序执行的结果是:

    这些特性使得我们在通常情况下,可以将枚举作为类来使用。

职责链模式的枚举实现

||| 职责链模式:先创建一批用于解决目标问题的不同方法,并将它们链式排列。当一个请求到达时,它会顺着这条“链”向下走,直到遇到可以解决当前请求的方法。

        下面的例子描述了一个邮局模型:使用常规方法处理信件,当前方法不可行时,尝试其他方法,直到无法处理该信件为止(称为“死信”)。

    可以将每种处理方法视作一种策略,策略的列表组成了一条职责链。

【例子:邮局模型】

import onjava.Enums;

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} // 是否可读

    enum Address {INCORRECT, OK1, OK2, OK3, OK4, OK5, OK6}

    enum ReturnAddress {MISSING, OK1, OK2, OK3, OK4, OK5}

    // 创建枚举变量
    GeneralDelivery generalDelivery;
    Scannability scannability;
    Readability readability;
    Address address;
    ReturnAddress returnAddress;
    static long counter = 0;
    long id = counter++;

    @Override
    public String toString() {
        return "邮件" + id;
    }

    public String details() {
        return toString() +
                ",一般交付:" + generalDelivery + ",地址是否可以扫描:" + scannability +
                "\n地址是否可读:" + readability + ",目标地址:" + address +
                "\n返还地址:" + returnAddress;
    }

    // 用于生成测试邮件:
    // (Enums类可见笔记 进阶1-1)
    public static Mail randomMail() {
        Mail m = new Mail();

        // 为枚举实例随机赋值:
        m.generalDelivery =
                Enums.random(GeneralDelivery.class);
        m.scannability =
                Enums.random(Scannability.class);
        m.readability =
                Enums.random(Readability.class);
        m.address =
                Enums.random(Address.class);
        m.returnAddress =
                Enums.random(ReturnAddress.class);

        return m;
    }

    // count无需修改,可以使用final
    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();
                    }
                };
            }
        };
    }
}

public class PostOffice {
    // 使用职责链模式:
    enum MailHandler {
        GENERAL_DELIVERY {
            @Override
            boolean handle(Mail m) {
                switch (m.generalDelivery) {
                    case YES:
                        System.out.println(
                                "对" + m + "使用常规方法进行处理");
                        return true;
                    default:
                        return false;
                }
            }
        },
        MACHINE_SCAN {
            @Override
            boolean handle(Mail m) {
                switch (m.scannability) {
                    case UNSCANNABLE:
                        return false;
                    default:
                        switch (m.address) {
                            case INCORRECT:
                                return false;
                            default:
                                System.out.println("自动派送" + m);
                                return true;
                        }
                }
            }
        },
        VISUAL_INSPECTION {
            @Override
            boolean handle(Mail m) {
                switch (m.readability) {
                    case ILLEGIBLE:
                        return false;
                    default:
                        switch (m.address) {
                            case INCORRECT:
                                return false;
                            default:
                                System.out.println("常规派送" + m);
                                return true;
                        }
                }
            }
        },
        RETURN_TO_SENDER {
            @Override
            boolean handle(Mail m) {
                switch (m.returnAddress) {
                    case MISSING:
                        return false;
                    default:
                        System.out.println(
                                "将" + m + "返还给发送者");
                        return true;
                }
            }
        };

        abstract boolean handle(Mail m);
    }

    static void handle(Mail m) {
        for (MailHandler handler : MailHandler.values())
            if (handler.handle(m))
                return;
        System.out.println(m + "是一封死信");
    }

    public static void main(String[] args) {
        for (Mail mail : Mail.generator(5)) {
            System.out.println(mail.details());
            handle(mail);
            System.out.println("============");
        }
    }
}

        程序执行的结果是:

        职责链模式MailHandler枚举中得到应用,并且枚举的定义顺序决定了各个策略在邮件上的应用顺序。


状态机模式的枚举实现

||| 状态机的解释:

  1. 具有有限数量的特定状态。
  2. 每个状态都有输入。根据输入,可以实现状态之间的跳转,但也存在瞬态
  3. 当任务执行完毕后,立即跳出所有状态。

        用枚举表示状态机模式的一个优势是,枚举可以限制出现的状态集的大小:

【例子:自动售货机】

        首先创建一个用于输入的枚举:

import java.util.Random;

public enum Input {
    CENT(1), COIN(10), TEN_COIN(100),
    TOOTHBRUSH(150.0f), TOWEL(200.0f), SOAP(80.0f),
    ABORT_TRANSACTION {
        @Override
        public float amount() {
            throw new RuntimeException("ABORT.amount()");
        }
    },
    STOP { // STOP必须是最后一个实例

        @Override
        public float amount() {
            throw new RuntimeException("SHUT_DOWN.amount()");
        }
    };

    float value;

    Input(float value) {
        this.value = value;
    }

    Input() {
    }

    float amount() { // 单位为:毛
        return value;
    }

    static Random rand = new Random(47);

    public static Input randomSelection() {
        // 不包括STOP:
        return values()[rand.nextInt(values().length - 1)];
    }
}

        amount()会返回物品对应的价格。由于枚举的限制,amount()会作用于所有的枚举实例。很显然,对最后两个实例调用它并不合适,因此这里需要重写amount()

        接下来实现自动售货机的状态机:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.function.Supplier;
import java.util.stream.Collectors;

enum Category {

    MONEY(Input.CENT, Input.COIN, Input.TEN_COIN),
    ITEM_SELECTION(Input.TOOTHBRUSH, Input.TOWEL, 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 float amount = 0;
    private static Input selection = null;

    enum StateDuration { // 用于标记enum中的瞬态
        TRANSIENT;
    }

    enum State {
        RESTING { // 休眠中
            @Override
            void next(Input input) {
                switch (Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        state = ADDING_MONEY; // 重新赋值state
                        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("当前余额不足以购买" + 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("这是你购买的物品:" + selection);
                amount -= selection.amount();
                state = GIVING_CHANGE;
            }
        },
        GIVING_CHANGE(StateDuration.TRANSIENT) { // 找钱
            @Override
            void next() {
                if (amount > 0) {
                    System.out.println("这是找零:" + amount);
                    amount = 0;
                }
                state = RESTING;
            }
        },
        TERMINAL { // 关闭机器
            @Override
            void output() {
                System.out.println("停止运行");
            }
        };

        // 判断当前状态是否为瞬态
        private boolean isTransient = false;

        State() {
        }

        State(StateDuration trans) {
            isTransient = true; // 说明当前处于瞬态
        }

        void next(Input input) {
            throw new RuntimeException("仅应该对非瞬态调用next(Input input)");
        }

        void next() {
            throw new RuntimeException("仅应该对StateDuration.TRANSIENT(瞬态)调用next()");
        }

        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);
    }
}

class RandomInputSupplier implements Supplier<Input> {
    @Override
    public Input get() {
        return Input.randomSelection();
    }
}

// 从文件输入:
class FileInputSupplier implements Supplier<Input> {
    private Iterator<String> input;

    FileInputSupplier(String fileName) {
        try {
            input = Files.lines(Paths.get(fileName)) // 行级的Stream流
                    .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());
    }
}

        若不从文件导入数据,则售货机在开机后就会一直运行下去。为了完整演示整个状态机的流程,这里使用文件输入。测试文件如下:

CENT; TEN_COIN; TOOTHBRUSH;
COIN; TEN_COIN; TEN_COIN; SOAP;
TEN_COIN;  TEN_COIN; CENT; COIN; TOWEL;
ABORT_TRANSACTION;
STOP;

        对应该文件,程序执行的结果是:

        在这种模式中,枚举在switch上发挥了它的功能。

        在VendingMachine类中存在着两个瞬态:DISPENSINGGIVING_CHANGE。状态机会在各种状态之间移动,知道不再处于某个瞬态中为止(对这个例子而言,“取出商品”和“找零”的动作是瞬态,售货机不应该停留在这些状态中)。

    然而,这种设计存在一个限制:State的枚举实例是静态的,这意味着这些实例只能访问其外部类(即VendingMachine)中的静态字段。这意味着只能存在一个VendingMachine实例。

多路分发

        若一个程序中存在多个交互类型,这个程序有可能变得很混乱。举一个例子:

public abstract class Number {
    abstract Number plus(Number number);
    abstract Number multiply(Number number);
    //...
}

此处的Number类是某个系统中所有数值类型的基类,其中包括一些进行数值运算所需要的方法。

        现在假设ab分别属于Number的某个子类,并且都使用Number引用指示它们。若要处理语句a.plus(b),一个简单的想法是我们需要同时知道ab的具体类型。遗憾的是,Java只能进行单路分发,这意味着我们只能对一个类型未知的对象进行操作

        多路分发并不是同时对多个类型未知的对象进行操作,而是进行了多次的方法调用

    使用多路分发,就必须对每个类型都执行虚拟调用。并且,若操作发生在不同的交互类型层次结构中,则还需要对每个层次结构都执行虚拟操作。

        接下来通过一个“猜拳”的例子展示多路分发的使用:

【例子:“猜拳”游戏】

        首先确定“猜拳”的三种结果:

public enum Outcome {
    WIN, // 赢
    LOSE, // 输
    DRAW // 平局
}

        接下来实现多路分发的接口:

public interface Item {
    Outcome compete(Item it);

    Outcome eval(Paper p);

    Outcome eval(Scissors p);

    Outcome eval(Rock p);
}

        接下来为“石头”、“剪刀”和“布”分别匹配它们的胜负情况:

        然后,通过调用compete()方法,就可以简单地判断“猜拳”的胜负:

import java.util.Random;

public class RoShamBo1 {
    static final int SIZE = 10;
    private static Random rand = new Random(47);

    public static Item newItem() {
        switch (rand.nextInt(3)) {
            default: // 必须有的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());
    }
}

        程序执行的结果是:

        在match()方法中,程序可以知道a的类型,进而调用对应的compete()方法。在compete()中调用eval()方法时,会向其中传入一个this引用,这个this引用用于保留原本a的类型。

    如上所示,多路分发的构筑较为麻烦。但好处显而易见:我们在调用中保持住了语法的优雅。

        因为枚举实例不是类型,因此我们无法通过重载eval()方法的方式实现目标。但还是有别的方法可以进行实现。 

1、使用枚举类型实现分发

        一种方法是在初始化枚举实例时进行操作。通过这种方式,我们最终可以得到一个类似于查询表的结构:

【例子:初始化枚举实现“猜拳”】

import static enums.Outcome.*;

// 规定Competitor接口,通过接口来操作枚举:
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 competitor) {
        switch (competitor) {
            default:
            case ROCK: // 若对方出的是石头
                return vROCK; // 那么就返回当前对象遇到石头时会得出的结果
            case PAPER: // 布
                return vPAPER;
            case SCISSORS: // 剪刀
                return vSCISSORS;
        }
    }

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo2.class, 10); // 独立出来的方法,用于操作猜拳
    }
}

        程序执行的结果是:

        书本在此处将原本用于操作枚举的代码分隔开来,以此增加了代码的复用率。以下是复用的代码(Competitor接口及RoShamBo类)的实现:

【例子:用于操作枚举的工具】

        为了统一现有的枚举及之后会出现的枚举的操作,可以使用Competitor接口:

public interface Competitor<T extends Competitor<T>> {
    Outcome compete(T competitor);
}

        我们规定,所有的“石头剪刀布”枚举都来自于这种竞争者(Competitor)关系,自限定类型(笔记18-4)可以很好地满足这一点。

    与一般的泛型相比,自限定类型对继承关系有更加严格的要求。对于上述的compete()方法而言,它只会接受Competitor及其子类。

         然后是生成随机结果的RoShamBo类:

import onjava.Enums;

public class RoShamBo extends EnumClass {
    // 使用了自限定类型:
    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));
    }
}

        这个类包含了一个用于接收的paly()方法和真正调用枚举操作的match()方法。play()方法规定,传入的数据必须是一个实现了Competitor接口的枚举。

        需要注意一点:由于play()方法不需要返回值,换言之,在离开泛型的边界上并没有需要使用到类型参数T的地方,因此参数T在这里似乎是不必要的。但如果将Class<T>替换为Class<?>这种通配符形式,就会发生报错:

这是因为通配符【?】无法继承多个基类,因此这里只能使用类型参数T


2、使用常量特定方法实现分发

        遗憾的是,枚举本身并不能作为对象类型使用。因此我们没有办法通过传递实例来进行多路分发。在这里,一个好的办法是使用switch语句:

【例子:使用switch分发】

import static enums.Outcome.*;

public enum RoShamBo3 implements Competitor<RoShamBo3> {
    PAPER {
        @Override
        public Outcome compete(RoShamBo3 competitor) {
            switch (competitor) {
                default:
                case PAPER:
                    return DRAW;
                case SCISSORS:
                    return LOSE;
                case ROCK:
                    return WIN;
            }
        }
    },
    SCISSORS {
        @Override
        public Outcome compete(RoShamBo3 competitor) {
            switch (competitor) {
                default:
                case PAPER:
                    return WIN;
                case SCISSORS:
                    return DRAW;
                case ROCK:
                    return LOSE;
            }
        }
    },
    ROCK {
        @Override
        public Outcome compete(RoShamBo3 competitor) {
            switch (competitor) {
                default:
                case PAPER:
                    return LOSE;
                case SCISSORS:
                    return WIN;
                case ROCK:
                    return DRAW;
            }
        }
    };

    // 此处的方法声明只是为了便于理解:
    @Override
    public abstract Outcome compete(RoShamBo3 competitor);

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo3.class, 10);
    }
}

        程序执行的结果与上一个例子相同:

        这种写法无疑是繁琐的。因此,我们可以尝试对其进行优化。用三目运算符来取代switch语句,压缩代码空间:

【例子:使用三目运算符进行优化】

public enum RoShamBo4 implements Competitor<RoShamBo4> {
    ROCK {
        @Override
        public Outcome compete(RoShamBo4 competitor) {
            return compete(SCISSORS, competitor);
        }
    },
    SCISSORS {
        @Override
        public Outcome compete(RoShamBo4 competitor) {
            return compete(PAPER, competitor);
        }
    },
    PAPER {
        @Override
        public Outcome compete(RoShamBo4 competitor) {
            return compete(ROCK, competitor);
        }
    };

    // 重载的compete()方法
    // 第一个参数表示的是胜利条件
    Outcome compete(RoShamBo4 loser, RoShamBo4 competitor) {
        return ((competitor == this) ? Outcome.DRAW : // 通过this确定平局条件
                (competitor == loser) ? Outcome.WIN : // 通过传入参数确定胜利条件
                        Outcome.LOSE);  // 那么剩下的就是失败条件了
    }

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo4.class, 10);
    }
}

        由于输出结果是一致的,因此此处不再展示。

    当然,更加简短的代码有可能带来更大的理解成本。在大型的系统开发过程中应该要注意这一点。


3、使用EnumMap实现分发

        EnumMap是专门为enum设计的高效Map。使用它,我们可以更好地实现多路分发。具体而言,当我们需要进行两路分发时,我们可以使用嵌套的EnumMap

【例子:使用EnumMap实现分发】

import java.util.EnumMap;

import static enums.Outcome.*;

public enum RoShamBo5 implements Competitor<RoShamBo5> {
    PAPER, SCISSORS, ROCK;

    // 使用Class进行初始化,规定Map的键值必须是RoShamBo5类型
    static EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>>
            table = new EnumMap<>(RoShamBo5.class);

    static {
        // 使用for循环,为table每个键创建其对应的对象
        for (RoShamBo5 it : RoShamBo5.values())
            table.put(it, new EnumMap<>(RoShamBo5.class));
        // 为对象填充具体的信息
        initRow(PAPER, DRAW, LOSE, WIN);
        initRow(SCISSORS, WIN, DRAW, LOSE);
        initRow(ROCK, LOSE, WIN, DRAW);
    }

    static void initRow(RoShamBo5 it,
                        Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {
        EnumMap<RoShamBo5, Outcome> row =
                RoShamBo5.table.get(it); // get()方法会返回指定键对应的值(一个EnumMap的对象)
        row.put(RoShamBo5.PAPER, vPAPER);
        row.put(RoShamBo5.SCISSORS, vSCISSORS);
        row.put(RoShamBo5.ROCK, vROCK);
    }

    @Override
    public Outcome compete(RoShamBo5 competitor) {
        return table.get(this).get(competitor); // 两次get(),两次分发
    }

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo5.class, 10);
    }
}

        这里需要注意的是static子句。我们需要在RoShamBo5的其余部分初始化完毕之前,将所有的胜负信息填充到table里面。因此需要使用静态的初始化方式。


4、使用二维数组实现分发

        在“石头剪刀布”中,每一个枚举实例都会对应一个固定的值(可以用ordinal()获取)。这使得使用二维数组实现两路分发成为可能:

【例子:使用二维数组实现分发】

import static enums.Outcome.*;

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()][this.ordinal()];
    }

    public static void main(String[] args) {
        RoShamBo.play(RoShamBo6.class, 10);
    }
}

        这种写法有着极高的效率,以及简短易懂的代码。但与之相对,它的缺点也正是在于其使用了数组:一方面,在面对更加大量的数据时,使用数组或许会漏掉某些麻烦的情况;除此之外,这种写法更为死板——从数组中得到的依旧只是一个常量的结果。

    针对第二个缺点,我们可以使用函数对象来进行一些变化,使得数组不再那么僵硬。并且,在处理特定问题时,“表驱动模式”可以发挥出强大的力量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/294602.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软考 软件设计师 查漏补缺

说明建模图 1-1 和图 1-2 是如何保持数据流图平衡。 答&#xff1a;图 1-1 &#xff08;或父图&#xff09;中某加工的输入输出数据流必须与图 1-2 &#xff08;或子图&#xff09;的输入输出数据流在数量和名 字上相同&#xff1b;图 1-1 &#xff08;或父图&#xff09;中的…

智能座舱的下一个价值“爆点”——让“光”更智能

汽车智能化快速升级&#xff0c;智能座舱作为人机交互的主要窗口&#xff0c;交互模态、用户体验也呈现多维度升级。 例如&#xff0c;今年下半年上市的多款高端智能车型纷纷基于高性能座舱硬件平台&#xff0c;集成了AR-HUD、DMS/OMS等高阶功能&#xff0c;同时结合超大屏/多…

新年启新程 | 开门红!菊风中标重庆三峡银行双录及产品销售可回溯系统项目

INTRODUCTION 近年来&#xff0c;随着人们需求的转变和金融科技的高速发展&#xff0c;银行开始朝着数智化方向转型。为顺应客户行为变迁&#xff0c;银行同业积极构建远程银行云服务生态。同时&#xff0c;面对业务的升级以及新的监管要求&#xff0c;现有音视频功能难以满足…

用友U8 Cloud smartweb2.RPC.d SQL注入漏洞

产品介绍 用友U8cloud是用友推出的新一代云ERP&#xff0c;主要聚焦成长型、创新型、集团型企业&#xff0c;提供企业级云ERP整体解决方案。它包含ERP的各项应用&#xff0c;包括iUAP、财务会计、iUFO cloud、供应链与质量管理、人力资源、生产制造、管理会计、资产管理&#…

只有jar包如何调试修改JDK底层源码

背景 有时候在阅读JDK源码的时候&#xff0c;需要调试修改源码&#xff0c;但又只有jar包。这个时候我们可以借助JAVA的endorsed技术。在官方文档如下描述。 Specifying the -Djava.endorsed.dirslib/endorsed system property on the Java command line will force the JVM…

后端中的Dao层、Service层、Impl层、utils层、Controller层

Java Dao层 dao层叫数据访问层&#xff0c;全称为data access object&#xff0c;属于一种比较底层&#xff0c;比较基础的操作&#xff0c;具体到对于某个表、某个实体的增删改查&#xff0c;对外提供稳定访问数据库的方法 Mapper:&#xff08;DAO&#xff09; 访问数据库&am…

22款奔驰GLS450升级香氛负离子 车载香薰

奔驰原厂香氛系统激活原车自带系统&#xff0c;将香气加藏储物盒中&#xff0c;通过系统调节与出风口相结合&#xff0c;再将香味传达至整个车厢&#xff0c;达到净化车厢空气的效果&#xff0c;让整个车厢更加绿色健康&#xff0c;清新淡雅。星骏汇小许Xjh15863 产品功能&…

Linux--系统安全及应用

1.1账号安全控制 用户账号是计算机使用者的身份凭证或标识&#xff0c;每个要访问系统资源的人&#xff0c;必须凭借其用户账号才能进入计算机。在Linux系统中&#xff0c;提供了多种机制来确保用户账号的正当、安全使用。 1.系统账号清理 在Linux系统中&#xff0c;除了用户手…

pod 基础 2

pod 进阶 探针 poststart prestop pod的生命周期开始&#xff1a; 重启&#xff1a;k8s的pod重启策略 deployment的yaml文件只能是Always pod的yaml三种模式都可以。 OnFailure&#xff1a;只有状态码非0才会重启&#xff0c;正常退出是不重启的 Never&#xff1a;正常退…

由浅入深理解C#中的事件

目录 本文较长&#xff0c;给大家提供了目录&#xff0c;可以直接看自己感兴趣的部分。 前言有关事件的概念示例​ 简单示例​ 标准 .NET 事件模式​ 使用泛型版本的标准 .NET 事件模式​ 补充总结 参考前言 前面介绍了C#中的委托&#xff0c;事件的很多部分都与委托…

sqlserver工具插入表语法into新表问题

文章目录 sqlserver工具插入表语法into新表问题 sqlserver工具插入表语法into新表问题 into新表问题 SELECT 1 AS FID, AS FNUMBER,1 AS FVALUE,A AS FVALUE2,名字 AS FNAME, 你的全名 FFULLNAME INTO t_user_mmINSERT INTO t_user_mm VALUES(2,2,2,B,懒人,懒人咖)INSERT I…

FreeRTOS——内存管理知识总结及实战

1 freeRTOS动态创建与静态创建 动态创建&#xff1a;从FreeRTOS 管理的内存堆中申请创建对象所需的内存&#xff0c;在对象删除后&#xff0c; 这块内存释放回FreeRTOS管理的内存堆中 静态创建&#xff1a;需用户提供各种内存空间&#xff0c;并且使用静态方式占用的内存空间一…

VCoder:大语言模型的眼睛

简介 VCoder的一个视觉编码器&#xff0c;能够帮助MLLM更好地理解和分析图像内容。提高模型在识别图像中的对象、理解图像场景方面的能力。它可以帮助模型显示图片中不同物体的轮廓或深度图&#xff08;显示物体距离相机的远近&#xff09;。还能更准确的理解图片中的物体是什…

(Java基础知识综合)

进制转换&#xff1a; 其他转10 10转其他 2进制8和16 8和16转2 位运算&#xff1a; >> 除于2 <<乘以2 近似值 空心金字塔 this关键字还可以用于访问父类中的属性和方法

axios 二次封装 设置token

// 公有 axios.defaults.headers.common[Y-Auth-Token]eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 设置后 并发起请求&#xff0c;你可以在浏览器控制台…

电脑提示“KBDRU1.DLL文件缺失”,导致游戏和软件无法启动运行,快速修复方法

看到很多小伙伴&#xff0c;在问电脑启动某些软件或游戏的时候&#xff0c;提示“KBDRU1.DLL文件缺失&#xff0c;软件无法启动&#xff0c;请尝试重新安装&#xff0c;解决问题”&#xff0c;不知道应该怎么办&#xff1f; 首先&#xff0c;我们要先了解“KBDRU1.DLL文件”是什…

数据结构学习 jz16 数值的整数次方

关键词&#xff1a;快速幂 位运算 之前已经学过快速幂了&#xff0c;所以只是回忆。快速幂有递归版和非递归版。 题目&#xff1a; 这道题和之前的快速幂的区别是 n可能为负数。分类讨论即可。 思路&#xff1a; 区分正负&#xff1a; if (n < 0) return POW(1.0 / x, …

ASUS华硕ROG幻14笔记本2023款G14 GA402XV原装出厂Win11系统

华硕幻14锐龙R9-7940HS笔记本电脑原厂Windows11.22H2系统 适用型号&#xff1a;GA402XU、GA402XV、GA402XI、GA402XY、GA402XZ 链接&#xff1a;https://pan.baidu.com/s/1sMva1u7D8uFoGnm2Hjrdug?pwdho91 提取码&#xff1a;ho91 原厂系统自带所有驱动、出厂主题壁纸、…

AWS EKS1.26+kubesphere3.4.1

1、前提准备 1台EC2服务器Amazon Linux2&#xff0c;设置admin的角色 安装 aws cli V2 ​ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"unzip awscliv2.zipsudo ./aws/installexport PATH/usr/local/bin:$PATHsou…

pod进阶版(2)

startupProbe启动探针 如果探测失败,pod的状态是notready&#xff0c;启动探针会重启容器 启动探针没有成功之前&#xff0c;后续的探针都不会执行。启动探针成功之后&#xff0c;在pod的后续生命周期不会用启动探针 exec方式 正确示范 apiVersion: v1 kind: Pod metadata…