主頁 > 後端開發 > Java 列舉 enum 詳解

Java 列舉 enum 詳解

2020-11-25 13:41:13 後端開發


本文部分摘自 On Java 8


列舉型別

Java5 中添加了一個 enum 關鍵字,通過 enum 關鍵字,我們可以將一組擁有具名的值的有限集合創建為一種新的型別,這些具名的值可以作為常規的程式組件使用,例如:

public enum Spiciness {
    NOT, MILD, MEDIUM, HOT, FLAMING
}

這里創建了一個名為 Spiciness 的列舉型別,它有 5 個值,由于列舉型別的實體是常量,因此按照命名慣例,它們都用大寫字母表示(如果名稱中含有多個單詞,使用下劃線分隔)

要使用 enum,需要創建一個該型別的參考,然后將其賦值給某個實體:

public class SimpleEnumUse {
    public static void main(String[] args) {
        Spiciness howHot = Spiciness.MEDIUM;
        System.out.println(howHot);
    }
}
// 輸出:MEDIUM

在 switch 中使用 enum,是 enum 提供的一項非常便利的功能,一般來說,在 switch 中只能使用整數值,而列舉實體天生就具備整數值的次序,并且可以通過 ordinal() 方法取得其次序,因此我們可以在 switch 陳述句中使用 enum

一般情況下我們必須使用 enum 型別來修飾一個 enum 實體,但是在 case 陳述句中卻不必如此,下面的例子使用 enum 構造了一個模擬紅綠燈狀態變化:

enum Signal { GREEN, YELLOW, RED, }

public class TrafficLight {
    
    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 < 7; i++) {
            System.out.println(t);
            t.change();
        }
    }
}

列舉的基本特性

Java 中的每一個列舉都繼承自 java.lang.Enum 類,所有列舉實體都可以呼叫 Enum 類的方法

呼叫 enum 的 values() 方法,回傳 enum 實體的陣列,而且該陣列中的元素嚴格保持其在 enum 中宣告時的順序,因此你可以在回圈中使用 values() 回傳的陣列

enum Shrubbery { GROUND, CRAWLING, HANGING }
public class EnumClass {
    public static void main(String[] args) {
        for(Shrubbery s : Shrubbery.values()) {
            System.out.println(s);
            // 回傳每個列舉實體在宣告時的次序
            System.out.println(s.ordinal());
            // 回傳與此列舉常量的列舉型別相對應的 Class 物件
            System.out.println(s.getDeclaringClass());
            // 回傳列舉實體宣告時的名字,效果等同于直接列印
            System.out.println(s.name());
            ...
        }
    }
}
// 輸出:
// GROUND
// 0
// GROUND
// CRAWLING
// 1
// CRAWLING
// HANGING
// 2
// HANGING

可以使用 == 來比較 enum 實體,編譯器會自動為你提供 equals() 和 hashCode() 方法,同時,Enum 類實作了 Comparable 介面,所以它具有 compareTo() 方法,同時,它還實作了 Serializable 介面

ValueOf() 方法是在 Enum 中定義的 static 方法,根據給定的名字回傳相應的 enum 實體,如果不存在給定名字的實體,將拋出例外

Shrubbery shrub = Enum.valueOf(Shrubbery.class, "HANGING");

我們再來看看 values() 方法,為什么要說這個呢?前面提到,編譯器為你創建的 enum 類都繼承自 Enum 類,然而,如果你研究一下 Enum 類就會發現,它并沒有 values() 方法,可我們明明已經用過該方法了呀,難道是這個方法被藏起來了?答案是,values() 是由編譯器添加的 static 方法,編譯器還會為創建的列舉類標記為 static final,所以無法被繼承

由于 values() 方法是由編譯器插入到 enum 定義中的 static 方法,所以,如果你將 enum 實體向上轉型為 Enum,那么 values() 方法就不可用了,不過,在 Class 中有一個 getEnumConstants() 方法,所以即便 Enum 介面中沒有 values() 方法,我們仍然可以通過 Class 物件取得所有 enum 實體

enum Search { HITHER, YON }
public class UpcastEnum {
    public static void main(String[] args) {
        for(Enum en : e.getClass().getEnumConstants())
            System.out.println(en);
    }
}

因為 getEnumConstants() 是 Class 上的方法,所以你甚至可以對不是列舉的類呼叫此方法,只不過,此時該方法回傳 null


方法添加

除了不能繼承自一個 enum 之外,我們基本上可以將 enum 看作一個常規的類,也就是說我們可以向 enum 中添加方法,enum 甚至可以有 main() 方法

我們希望每個列舉實體能夠回傳對自身的描述,而不僅僅只是默認的 toString() 實作,這只能回傳列舉實體的名字,為此,你可以提供一個構造器,專門負責處理這個額外的資訊,然后添加一個方法,回傳這個描述資訊,看一看下面的示例:

public enum OzWitch {
    
    WEST("Miss Gulch, aka the Wicked Witch of the West"),
    NORTH("Glinda, the Good Witch of the North"),
    EAST("Wicked Witch of the East, wearer of the Ruby "),
    SOUTH("Good by inference, but missing");	// 必須在 enum 實體序列的最后添加一個分號
    
    private String description;
    
    private OzWitch(String description) {
        this.description = description;
    }
    
    public String getDescription() { return description; }
    
    public static void main(String[] args) {
        for(OzWitch witch : OzWitch.values())
            System.out.println(witch + ": " + witch.getDescription());
    }
}

在這個例子中,我們有意識地將 enum 的構造器宣告為 private,但對于它的可訪問性而言,并沒有什么變化,因為(即使不宣告為 private)我們只能在 enum 定義的內部使用其構造器創建 enum 實體,一旦 enum 的定義結束,編譯器就不允許我們再使用其構造器來創建任何實體了

如果希望覆寫 enum 中的方法,例如覆寫 toString() 方法,與覆寫一般類的方法沒有區別:

public enum SpaceShip {
    
    SCOUT, CARGO, TRANSPORT,
    CRUISER, BATTLESHIP, MOTHERSHIP;
    
    @Override
    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);
    }
}

實作介面

我們已經知道,所有的 enum 都繼承自 Java.lang.Enum 類,由于 Java 不支持多重繼承,所以 enum 不能再繼承其他類,但可以實作一個或多個介面

enum CartoonCharacter implements Supplier<CartoonCharacter> {
    
    SLAPPY, SPANKY, PUNCHY,
    SILLY, BOUNCY, NUTTY, BOB;
    
    private Random rand = new Random(47);
    
    @Override
    public CartoonCharacter get() {
        return values()[rand.nextInt(values().length)];
    }
}

通過實作一個供給型介面,就可以通過呼叫 get() 方法得到一個隨機的列舉值,我們可以借助泛型,使隨機選擇這個作業更加一般化,成為一個通用的工具類

public class Enums {
    
    private static Random rand = new Random(47);

    public static <T extends Enum<T>> T random(Class<T> ec) {
        return random(ec.getEnumConstants());
    }

    public static <T> T random(T[] values) {
        return values[rand.nextInt(values.length)];
    }
}

<T extends Enum> 表示 T 是一個 enum 實體,而將 Class 作為引數的話,我們就可以利用 Class 物件得到 enum 實體的陣列了,多載后的 random() 方法只需使用 T[] 作為引數,因為它并不會呼叫 Enum 上的任何操作,它只需從陣列中隨機選擇一個元素即可,這樣,最終的回傳型別正是 enum 的型別

使用介面可以幫助我們實作將列舉元素分類的目的,舉例來說,假設你想用 enum 來表示不同類別的食物,同時還希望每個 enum 元素仍然保持 Food 型別,那可以這樣實作:

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

對于 enum 而言,實作介面是使其子類化的唯一辦法,所以嵌入在 Food 中的每個 enum 都實作了 Food 介面,現在,在下面的程式中,我們可以說“所有東西都是某種型別的 Food"

public class TypeOfFood {
    
    public static void main(String[] args) {
        
        Food food = Appetizer.SALAD;
        food = MainCourse.LASAGNE;
        food = Dessert.GELATO;
        food = Coffee.CAPPUCCINO;
    }
}

如果 enum 的數量太多,那么一個介面中的代碼量可能就很大,我們可以利用 getEnumConstants() 方法,根據某個 Class 物件取得某個 Food 子類的所有 enum 實體

public enum Course {
    
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.class),
    COFFEE(Food.Coffee.class);
    
    private Food[] values;
    
    private Course(Class<? extends Food> kind) {
        values = kind.getEnumConstants();
    }
    
    // 隨機獲取 Food 子類的某個 enum 實體
    public Food randomSelection() {
        return Enums.random(values);
    }
}

如果對內部類熟悉的話,我們還可以使用更加簡潔的管理列舉的方式,就是將第一種方式中的介面嵌套在列舉里,使得代碼具有更清晰的結構

enum SecurityCategory {
    
    STOCK(Security.Stock.class),
    BOND(Security.Bond.class);
    
    Security[] values;
    
    SecurityCategory(Class<? extends Security> kind) {
        values = kind.getEnumConstants();
    }
    
    interface Security {
        enum Stock implements Security {
            SHORT, LONG, MARGIN
        }
        enum Bond implements Security {
            MUNICIPAL, JUNK
        }
    }
    
    public Security randomSelection() {
        return Enums.random(values);
    }
    
    public static void main(String[] args) {
        for(int i = 0; i < 10; i++) {
            SecurityCategory category = Enums.random(SecurityCategory.class);
            System.out.println(category + ": " + category.randomSelection());
        }
    }
}

EnumSet

EnumSet 是一個用來操作 Enum 的集合,可以用來存放屬于同一列舉型別的列舉常量,其中元素存放的次序決定于 enum 實體定義時的次序,EnumSet 的設計初衷是為了替代傳統的基于 int 的“位標志”,傳統的“位標志”可以用來表示某種“開/關”資訊,不過,使用這種標志,我們最終操作的只是一些 bit,而不是這些 bit 想要表達的概念,因此很容易寫出令人費解的代碼

既然 EnumSet 要替代 bit 標志,那么它的性能應該要做到與使用 bit 一樣高效才對,EnumSet 的基礎是 long,一個 long 值有 64 位,一個 enum 實體只需一位 bit 表示其是否存在, 也就是說,在不超過一個 long 的表達能力的情況下,你的 EnumSet 可以應用于最多不超過 64 個元素的 enum,如果 enum 超過了 64 個元素,EnumSet 會在必要時增加一個 long

EnumSet 的方法如下:

方法 作用
allOf(Class elementType) 創建一個包含指定列舉類里所有列舉值的 EnumSet 集合
complementOf(EnumSet e) 創建一個元素型別與指定 EnumSet 里元素型別相同的 EnumSet 集合,新 EnumSet 集合包含原 EnumSet 集合所不包含的的列舉值
copyOf(Collection c) 使用一個普通集合來創建 EnumSet 集合
copyOf(EnumSet e) 創建一個指定 EnumSet 具有相同元素型別、相同集合元素的 EnumSet 集合
noneOf(Class elementType) 創建一個元素型別為指定列舉型別的空 EnumSet
of(E first,E…rest) 創建一個包含一個或多個列舉值的 EnumSet 集合,傳入的多個列舉值必須屬于同一個列舉類
range(E from,E to) 創建一個包含從 from 列舉值到 to 列舉值范圍內所有列舉值的 EnumSet 集合

示例代碼:

public enum AlarmPoints {
    STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
    OFFICE4, BATHROOM, UTILITY, KITCHEN
}

public class EnumSets {
    public static void main(String[] args) {
        EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);
        points.add(BATHROOM);
        System.out.println(points);
        points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
        System.out.println(points);
        points = EnumSet.allOf(AlarmPoints.class);
        points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
        System.out.println(points);
        points.removeAll(EnumSet.range(OFFICE1, OFFICE4));
        System.out.println(points);
        points = EnumSet.complementOf(points);
        System.out.println(points);
    }
}

EnumMap

EnumMap 是一種特殊的 Map,它要求 key 必須為列舉型別,value 沒有限制,底層由雙陣列實作(一個存放 key,另一個存放 value),同時,當 value 為 null 時會特殊處理為一個 Object 物件,和 EnumSet 一樣,元素存放的次序決定于 enum 實體定義時的次序

// key 型別
private final Class<K> keyType;

// key 陣列
private transient K[] keyUniverse;

// value 陣列
private transient Object[] vals;

// 鍵值對個數
private transient int size = 0;

// value 為 null 時對應的值
private static final Object NULL = new Object() {
    public int hashCode() {
        return 0;
    }
    public String toString() {
        return "java.util.EnumMap.NULL";
    }
};

由于 EnumMap 可以存放列舉型別,所以初始化時必須指定列舉型別,EnumMap 提供了三個建構式

// 使用指定的鍵型別創建一個空的列舉映射
EnumMap(Class<K> keyType);
// 創建與指定的列舉映射相同的鍵型別的列舉映射,最初包含相同的映射(如果有)
EnumMap(EnumMap<K,? extends V> m);
// 創建從指定映射初始化的列舉映射
EnumMap(Map<K,? extends V> m);

除此之外,EnumMap 與普通 Map 在操作上沒有區別,EnumMap 的優點在于允許程式員改變值物件,而常量相關的方法在編譯期就被固定了


常量特定方法

我們可以為 enum 定義一個或多個 abstract 方法,然后為每個 enum 實體實作該抽象方法

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 csm : values())
            System.out.println(csm.getInfo());
    }
}

在面向物件的程式設計中,不同的行為與不同的類關聯,通過常量相關的方法,每個 enum 實體可以具備自己獨特的行為,這似乎說明每個 enum 實體就像一個獨特的類,在上面的例子中,enum 實體似乎被當作其超類 ConstantSpecificMethod 來使用,在呼叫 getInfo() 方法時,體現出多型的行為

然而,enum 實體與類的相似之處也僅限于此了,我們并不能真的將 enum 實體作為一個型別來使用,因為每一個 enum 元素都是指定列舉型別的 static final 實體

除了 abstract 方法外,程式員還可以覆寫普通方法

public enum OverrideConstantSpecific {
    NUT, BOLT,
    WASHER {
        @Override
        void f() {
            System.out.println("Overridden method");
        }
    };
    void f() {
        System.out.println("default behavior");
    }
    public static void main(String[] args) {
        for(OverrideConstantSpecific ocs : values()) {
            System.out.print(ocs + ": ");
            ocs.f();
        }
    }
}

多路分發

現在有一個數學運算式 Number.plus(Number),我們知道 Number 是各種數字物件的超類,假設有 a 和 b 兩個 Number 型別的物件,根據上述的運算式代入得 a.plus(b),但你現在只知道 a、b 屬于 Number 型別,具體是什么數字你并不知道,有可能是整數、浮點數,根據不同的數字型別,執行數學操作后的結果應該也不一樣才對,怎么讓它們正確地互動呢?

如果你了解 Java 多型就知道,Java 多型的實作依賴的是 Java 的動態系結機制,在運行時發現物件的真實型別,但 Java 只支持單路分發,就是說如果要執行的操作包含不止一個型別未知的物件,那么 Java 的動態系結機制只能處理其中一個型別,a.plus(b) 涉及到兩個型別,自然無法解決我們的問題,所以我們必須自己來判定其他型別

解決問題的方法就是多路分發,上面的例子,由于只有兩個分發,一般稱為兩路分發,多型只能發生在方法呼叫時,所以,如果你想使用兩路分發,那么就必須有兩個方法呼叫:第一個方法呼叫決定第一個未知型別,第二個方法呼叫決定第二個未知的型別,程式員必須設定好某種配置,以便一個方法呼叫能夠引出其他方法呼叫,從而在這個程序中處理多種型別

來看一個猜拳的例子:

package enums;
public enum Outcome { WIN, LOSE, DRAW }	// 猜拳的結果:勝、負、平手
package enums;
import java.util.*;
import static enums.Outcome.*;	// 引入 enums,這樣就不用寫前綴 Outcome 了
interface Item {
    Outcome compete(Item it);
    Outcome eval(Paper p);
    Outcome eval(Scissors s);
    Outcome eval(Rock r);
}
class Paper implements Item {
    @Override
    public Outcome compete(Item it) {
        return it.eval(this);
    }
    @Override
    public Outcome eval(Paper p) { return DRAW; }
    @Override
    public Outcome eval(Scissors s) { return WIN; }
    @Override
    public Outcome eval(Rock r) { return LOSE; }
    @Override
    public String toString() { return "Paper"; }
}
class Scissors implements Item {
    @Override
    public Outcome compete(Item it) {
        return it.eval(this);
    }
    @Override
    public Outcome eval(Paper p) { return LOSE; }
    @Override
    public Outcome eval(Scissors s) { return DRAW; }
    @Override
    public Outcome eval(Rock r) { return WIN; }
    @Override
    public String toString() { return "Scissors"; }
}
class Rock implements Item {
    @Override
    public Outcome compete(Item it) {
        return it.eval(this);
    }
    @Override
    public Outcome eval(Paper p) { return WIN; }
    @Override
    public Outcome eval(Scissors s) { return LOSE; }
    @Override
    public Outcome eval(Rock r) { return DRAW; }
    @Override
    public String toString() { return "Rock"; }
}
public class RoShamBo1 {
    static final int SIZE = 20;
    private static Random rand = new Random(47);
    public static Item newItem() {
        switch(rand.nextInt(3)) {
            default:
            case 0: return new Scissors();
            case 1: return new Paper();
            case 2: return new Rock();
        }
    }
    public static void match(Item a, Item b) {
        System.out.println(
                a + " vs. " + b + ": " + a.compete(b));
    }
    public static void main(String[] args) {
        for(int i = 0; i < SIZE; i++)
            match(newItem(), newItem());
    }
}

上面就是多路分發的實作,它的好處在于避免判定多個物件的型別的冗余代碼,不過配置程序需要很多道工序,我們既然學習了列舉,自然可以考慮用列舉對代碼進行優化

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

public class RoShamBo {
    
    public static <T extends Competitor<T>> void match(T a, T b) {
        System.out.println(a + " vs. " + b + ": " + a.compete(b));
    }
    
    public static <T extends Enum<T> & Competitor<T>> 
        void play(Class<T> rsbClass, int size) {
        for(int i = 0; i < size; i++)
            match(Enums.random(rsbClass),Enums.random(rsbClass));
    }
}

public enum RoShamBo2 implements Competitor<RoShamBo2> {
    
    PAPER(DRAW, LOSE, WIN),
    SCISSORS(WIN, DRAW, LOSE),
    ROCK(LOSE, WIN, DRAW);
    
    private Outcome vPAPER, vSCISSORS, vROCK;
    
    RoShamBo2(Outcome paper, Outcome scissors, Outcome rock) {
        this.vPAPER = paper;
        this.vSCISSORS = scissors;
        this.vROCK = rock;
    }
    
    @Override
    public Outcome compete(RoShamBo2 it) {
        switch(it) {
            default:
            case PAPER: return vPAPER;
            case SCISSORS: return vSCISSORS;
            case ROCK: return vROCK;
        }
    }
    
    public static void main(String[] args) {
        RoShamBo.play(RoShamBo2.class, 20);
    }
}

也可以使用將 enum 用在 switch 陳述句中

import static enums.Outcome.*;
public enum RoShamBo3 implements Competitor<RoShamBo3> {
    PAPER {
        @Override
        public Outcome compete(RoShamBo3 it) {
            switch(it) {
                default:
                case PAPER: return DRAW;
                case SCISSORS: return LOSE;
                case ROCK: return WIN;
            }
        }
    },
    SCISSORS {
        @Override
        public Outcome compete(RoShamBo3 it) {
            switch(it) {
                default:
                case PAPER: return WIN;
                case SCISSORS: return DRAW;
                case ROCK: return LOSE;
            }
        }
    },
    ROCK {
        @Override
        public Outcome compete(RoShamBo3 it) {
            switch(it) {
                default:
                case PAPER: return LOSE;
                case SCISSORS: return WIN;
                case ROCK: return DRAW;
            }
        }
    };
    @Override
    public abstract Outcome compete(RoShamBo3 it);
    public static void main(String[] args) {
        RoShamBo.play(RoShamBo3.class, 20);
    }
}

對上述代碼還可以再壓縮一下

public enum RoShamBo4 implements Competitor<RoShamBo4> {
    ROCK {
        @Override
        public Outcome compete(RoShamBo4 opponent) {
            return compete(SCISSORS, opponent);
        }
    },
    SCISSORS {
        @Override
        public Outcome compete(RoShamBo4 opponent) {
            return compete(PAPER, opponent);
        }
    },
    PAPER {
        @Override
        public Outcome compete(RoShamBo4 opponent) {
            return compete(ROCK, opponent);
        }
    };
    Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {
        return ((opponent == this) ? Outcome.DRAW
                : ((opponent == loser) ? Outcome.WIN
                : Outcome.LOSE));
    }
    public static void main(String[] args) {
        RoShamBo.play(RoShamBo4.class, 20);
    }
}

使用 EnumMap 能夠實作真正的兩路分發,EnumMap 是為 enum 專門設計的一種性能非常好的特殊 Map,由于我們的目的是摸索出兩種未知的型別,所以可以用一個 EnumMap 的 EnumMap 來實作兩路分發:

package enums;
import java.util.*;
import static enums.Outcome.*;
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, 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);
        row.put(RoShamBo5.PAPER, vPAPER);
        row.put(RoShamBo5.SCISSORS, vSCISSORS);
        row.put(RoShamBo5.ROCK, vROCK);
    }
    @Override
    public Outcome compete(RoShamBo5 it) {
        return table.get(this).get(it);
    }
    public static void main(String[] args) {
        RoShamBo.play(RoShamBo5.class, 20);
    }
}

我們還可以進一步簡化實作兩路分發的解決方案,我們注意到,每個 enum 實體都有一個固定的值(基于其宣告的次序),并且可以通過 ordinal() 方法取得該值,因此我們可以使用二維陣列,將競爭者映射到競爭結果,采用這種方式能夠獲得最簡潔、最直接的解決方案

package enums;
import static enums.Outcome.*;
enum RoShamBo6 implements Competitor<RoShamBo6> {
    PAPER, SCISSORS, ROCK;
    private static Outcome[][] table = {
            { DRAW, LOSE, WIN }, // PAPER
            { WIN, DRAW, LOSE }, // SCISSORS
            { LOSE, WIN, DRAW }, // ROCK
    };
    @Override
    public Outcome compete(RoShamBo6 other) {
        return table[this.ordinal()][other.ordinal()];
    }
    public static void main(String[] args) {
        RoShamBo.play(RoShamBo6.class, 20);
    }
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/227628.html

標籤:其他

上一篇:使用 Docker 部署 Spring Boot 專案,帶勁!!

下一篇:go-zero 如何扛住流量沖擊(二)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more