Java1.5 中出現了枚舉類型。當一個值都在一個固定的范圍內變化,那就可以使用 enum 類型來定義。比如說,一周有七天,一年有四季。
沒有枚舉類的時候,我們用常量來定義一組范圍值的:
public static class Season {
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int AUTUMN = 3;
public static final int WINTER = 4;
}
通過常量定義的方式有這樣幾個缺點:
-
類型不安全。如
Season
類所示,程序執行過程中接收的是任意一個int
類型的值,完全可能傳入一個 1 到 4 之外的值,導致錯誤的出現。 -
一致性差。int 枚舉屬於編譯期常量,編譯完成后,代碼中引用的地方會直接將整數值寫入。也就是說,該 Int 枚舉被修改之后,所有引用它的程序都需要重新編譯。
-
類型無指意性。Seaon 枚舉值僅僅是一些無任何含義的整數值,調試期間僅僅是一些魔數。
定義一個 enum
《阿里巴巴Java開發手冊(華山版)》中建議我們這樣定義一個 enum 類:
【參考】枚舉類名帶上 Enum 后綴,枚舉成員名稱需要全大寫,單詞間用下划線隔開。
說明:枚舉其實就是特殊的類,域成員均為常量,且構造方法被默認強制是私有。
正例:枚舉名字為 ProcessStatusEnum 的成員名稱:SUCCESS / UNKNOWN_REASON。
【推薦】如果變量值僅在一個固定范圍內變化用 enum 類型來定義。
說明:如果存在名稱之外的延伸屬性應使用 enum 類型,下面正例中的數字就是延伸信息,表示一年中的第幾個季節。
由此,我們可以定義一個季節的枚舉類,如下所示:
public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int seq;
SeasonEnum(int seq) {
this.seq = seq;
}
public int getSeq() {
return seq;
}
}
enum 類
使用 enum 定義的枚舉類默認繼承 java.lang.Enum
:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
...
}
變量
// enum 實例的名字
private final String name;
// 定義enum實例的順序,從0開始計數
private final int ordinal;
方法
// 返回enum實例的名稱,eg. SeasonEnum.SPRING.name() - output: SPRING
public final String name() { return name;}
// 返回enum實例的順序,eg. SeasonEnum.SPRING.ordinal() - output: 0
public final int ordinal() { return ordinal;}
// enum實例 -> 字符串 eg. SeasonEnum.SPRING.toString() - output: SPRING
public String toString() { return name;}
// 比較是不是一個constant,即兩個enum實例的順序(oridnal)是否相同
public final int compareTo(E o) { ... return self.ordinal - other.ordinal; }
// 比較是不是同一個對象
public final boolean equals(Object other) { return this==other;}
// 返回聲明枚舉常量的類的類對象
public final Class<E> getDeclaringClass(){...}
// 返回指定name的enum實例
public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {}
// values()是編譯器產生的一個方法,API中沒有,可以用來遍歷
public static SeasonEnum getSeasonByOrdinal(int seq) {
for (SeasonEnum season: SeasonEnum.values()) {
if(season.getSeq() == seq) {
return season;
}
}
return null;
}
enum 的用法
用法一:定義常量
也就是最開始說的,enum 出現之后,用 enum 來代替常量類型。
public static class Season {
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int AUTUMN = 3;
public static final int WINTER = 4;
}
public enum SeasonEnum {
SPRING, SUMMER, AUTUMN, WINTER;
}
用法二:switch
Java1.5 推出了 enum,Java1.6 支持在 switch 中使用 enum 類型。
public enum OperationUseSwitch {
PLUS, MINUS, TIMES, DIVIDE;
double apply(double x, double y) {
switch (this) {
case PLUS:
return x + y;
case MINUS:
return x - y;
case TIMES:
return x * y;
case DIVIDE:
return x / y;
}
// 如果this不屬於上面四種操作符,拋出異常
throw new AssertionError("Unknown operation: " + this);
}
}
用法三:特定於常量的方法實現
特定於常量的方法實現指的是,在 enum 類中定義一個抽象方法,然后各個枚舉常量需要實現這個方法。這樣做的優點在於,相對於 switch 語句,抽象程度更高,每個 enum 實例都需要實現統一的方法,不會漏下。
public enum Operation {
PLUS {
double apply(double x, double y) {
return x + y;
}
},
MINUS {
double apply(double x, double y) {
return x - y;
}
},
TIMES {
double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
double apply(double x, double y) {
return x / y;
}
};
abstract double apply(double x, double y);
}
用法四:實現接口
enum 類都隱式得繼承自 java.lang.Enum
,而 Java 只支持單繼承,所有 enum 類不能再繼承其它類,但是可以實現接口。比如用法三中將統一的方法抽象出來,就可以使用接口來實現:
public interface Behavior {
double apply(double x, double y);
}
public enum Operation implements Behavior{
PLUS {
public double apply(double x, double y) {
return x + y;
}
},
MINUS {
public double apply(double x, double y) {
return x - y;
}
},
TIMES {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
public double apply(double x, double y) {
return x / y;
}
};
}
用法五:覆蓋 Enum 的方法
public class Test {
public enum Color {
RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLO("黃色", 4);
// 成員變量
private String name;
private int index;
// 構造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 覆蓋方法
@Override
public String toString() {
return this.index + "_" + this.name;
}
}
public static void main(String[] args) {
System.out.println(Color.RED.toString());
}
}
EnumSet 和 EnumMap
EnumSet 和 EnumMap 是兩個為枚舉而設計的集合。EnumSet保證集合中的元素不重復;EnumMap中的 key 是enum 類型,而 value 則可以是任意類型。
EnumMap
public class Herb {
public enum Type { ANNUAL, PERENNIAL, BIENNIAL }
private final String name;
private final Type type;
Herb(String name, Type type) {
this.name = name;
this.type = type;
}
@Override public String toString() {
return name;
}
}
public static void main(String[] args) {
Herb[] garden = { new Herb("Basil", Type.ANNUAL),
new Herb("Carroway", Type.BIENNIAL),
new Herb("Dill", Type.ANNUAL),
new Herb("Lavendar", Type.PERENNIAL),
new Herb("Parsley", Type.BIENNIAL),
new Herb("Rosemary", Type.PERENNIAL) };
// Using an EnumMap to associate data with an enum - Page 162
Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(
Herb.Type.class);
for (Herb.Type t : Herb.Type.values())
herbsByType.put(t, new HashSet<Herb>());
for (Herb h : garden)
herbsByType.get(h.type).add(h);
System.out.println(herbsByType);
}
EnumSet
EnumSet 是枚舉類型的高性能 Set 實現,它要求放入它的枚舉常量必須屬於同一枚舉類型。EnumSet 提供了許多工廠方法以便於初始化。
方法名稱 | 描述 |
---|---|
allOf(Class
|
創建一個包含指定枚舉類型中所有枚舉成員的 EnumSet 對象 |
complementOf(EnumSet
|
創建一個與指定 EnumSet 對象 s 相同的枚舉類型 EnumSet 對象, 並包含所有 s 中未包含的枚舉成員 |
copyOf(EnumSet
|
創建一個與指定 EnumSet 對象 s 相同的枚舉類型 EnumSet 對象, 並與 s 包含相同的枚舉成員 |
noneOf(<Class
|
創建指定枚舉類型的空 EnumSet 對象 |
of(E first,e...rest) | 創建包含指定枚舉成員的 EnumSet 對象 |
range(E from ,E to) | 創建一個 EnumSet 對象,該對象包含了 from 到 to 之間的所有枚 舉成員 |
EnumSet 作為 Set 接口實現,它支持對包含的枚舉常量的遍歷。
for(Operation op:EnumSet.range(Operation.PLUS,Operation.MULTIPLY)) {
doSomeThing(op);
}