枚舉類型 是一組固定常量組成合法值的類型,比如一年中的季節,一週中的星期。
int枚舉模式
不使用枚舉時定義常量的方法通常是這樣:
public final class Week {
public static final int WEEK_MONDAY = 1;
public static final int WEEK_TUESDAY = 2;
public static final int WEEK_WEDNESDAY = 3;
public static final int WEEK_THURSDAY = 4;
public static final int WEEK_FRIDAY = 5;
public static final int WEEK_SATURDAY = 6;
public static final int WEEK_SUNDAY = 7;
}
這種方法稱作int枚舉模式,這種方式有些缺點:
- 類型非安全,不能在編譯期間發現問題,比如
setWeek(10)
或者setWeek(MONTH_JANUARY)
在語法上都是合法的,但你的本意並非如此。 - 可讀性差,缺乏方便的方法把常量轉換爲有意義的字符串顯示。
java1.5開始java提供一種新的引用類型 – 枚舉類型(enum type)
定義枚舉類型以及簡單使用
/**
* 定義了加、減、乘、除 操作
*/
public enum Operation {
PLUS, MINUS, TIMES, DIVIDE
}
//計算並打印結果
public static void calc(int num1, int num2, Operation op) {
switch (op) {
case PLUS:
System.out.println(num1 + " + " + num2 + " = "+num1 + num2);
break;
case MINUS:
System.out.println(num1 - num2);
break;
case TIMES:
System.out.println(num1 * num2);
break;
case DIVIDE:
System.out.println(num1 / num2);
break;
default:
throw new RuntimeException();
}
}
//調用計算方法
calc(1, 2, Operation.PLUS);//輸出: 1 + 2 = 12
其中的
PLUS
MINUS
等都是Operation
類型,因此可以避免上面int枚舉模式中類型安全問題,
可以利用編譯器檢查出類型不匹配。
枚舉的一些特點
使用javap反編譯上面的Operation.class
得到下面代碼
public final class com.erick.hello.Operation extends java.lang.Enum<com.erick.hello.Operation> {
public static final com.erick.hello.Operation PLUS;
public static final com.erick.hello.Operation MINUS;
public static final com.erick.hello.Operation TIMES;
public static final com.erick.hello.Operation DIVIDE;
static {};
public static com.erick.hello.Operation[] values();
public static com.erick.hello.Operation valueOf(java.lang.String);
}
通過上面反編譯的結果可以知道:
- Operation隱式繼承了
java.lang.Enum
,Enum
實現了Comparable<E>
Serializable
這兩個接口。 - Operation不能再繼承其他類,但可以實現其他接口。
- Operation是final的,所以不能被擴展。但這不是真正原因,真正的限制應該是編譯器做的限制。
- 新生成了兩個方法
values()
valueOf(String)
- 定義的每一個枚舉值實際上都是一個
Operation
類型的實例
除了完善了int枚舉類型的不足之外,枚舉類型還可以添加任意的方法和屬性,並且可以實現任意接口。
添加任意方法的原因可能是想要將數據或者操作與對應的常量關聯。
爲枚舉增加屬性和方法
/**
* 定義了加、減、乘、除 操作
*/
public enum Operation {
//枚舉常量的定義必須在第一行,最後一個常量後面加分號,後面是定義的屬性和方法
PLUS("+"), MINUS("-"), TIMES("*"), DIVIDE("/");
private String op;
private Operation(String op) {
this.op = op;
}
public String getOP() {
return op;
}
public void calc(int num1, int num2) {
switch (this) {
case PLUS:
System.out.println(num1 + op + num2 + " = " + (num1 + num2));
break;
case MINUS:
System.out.println(num1 - num2);
break;
case TIMES:
System.out.println(num1 * num2);
break;
case DIVIDE:
System.out.println(num1 / num2);
break;
default:
throw new RuntimeException();
}
}
}
//測試
Operation.PLUS.calc(1, 2);
//輸出:1+2 = 3
上面的例子中如果想要增加一個開方運算,就需要增加一個開方枚舉,並在switch case 中加一個分支來做具體運算。
但是這樣很容易只添加了開方枚舉,但忘記了具體運算的實現,而且能夠順利編譯。下面的方法可以儘量避免這一情況。
public enum Operation {
PLUS("+") {
@Override
void calc(int num1, int num2) {
System.out.println(num1 + this.getOP() + num2 + " = " + (num1 + num2));
}
},
MINUS("-") {
@Override
void calc(int num1, int num2) {
System.out.println(num1 + this.getOP() + num2 + " = " + (num1 + num2));
}
};
private String op;
private Operation(String op) {
this.op = op;
}
public String getOP() {
return op;
}
abstract void calc(int num1, int num2);
}
枚舉中通用的方法
//比較此枚舉與指定對象的順序。
int compareTo(E o)
//返回與此枚舉常量的枚舉類型相對應的 Class 對象。
Class<E> getDeclaringClass()
//返回此枚舉常量的名稱,在其枚舉聲明中對其進行聲明。
String name()
//與name()返回一致
String toString()
//返回枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量序數爲零)。
int ordinal()
//返回帶指定名稱的指定枚舉類型的枚舉常量。
static <T extends Enum<T>> T valueOf(String name)
//測試
for(Operation o : Operation.values()){
System.out.println(o.ordinal() + " : " + o.name() + " : " + o.getDeclaringClass());
}
// 輸出:
// 0 : PLUS : class com.erick.hello.Operation
// 1 : MINUS : class com.erick.hello.Operation
// 2 : TIMES : class com.erick.hello.Operation
// 3 : DIVIDE : class com.erick.hello.Operation
ordinal()的使用
大多數情況下不需要使用這個方法,這個方法的設計是用於像EnumSet和EnumMap這種基於枚舉通用數據結構的,
除非是編寫這些數據結構,否則應該避免使用ordinal方法。如果需要枚舉與一個int值綁定,應該使用枚舉實例的屬性來綁定。
public enum Week {
WEEK_MONDAY(1), WEEK_TUESDAY(2);
private int num;
Week(int num) {
this.num = num;
}
}
使用接口模擬可伸縮的枚舉
枚舉類型不能被繼承,這是語言特性,而且枚舉是用來表示有限數量的對象,因此大多情況下並不需要擴展。
但有些情況,比如一些特特定操作,當下只包括了所有操作的一部分就可以滿足需求,
但需要更多操作時就需要擴展。
上面的計算器例子中,實現了計算器的加、減、乘、除操作,但之後又需要一個求冪的操作,
這時可以實現一個操作接口,來實現可擴展的特性。
//操作接口
public interface Operation {
void calc(int num1, int num2);
}
//基礎計算枚舉
public enum BasicOperation implements Operation{
//....省略了減法和乘除。
PLUS("+") {
@Override
public void calc(int num1, int num2) {
System.out.println(num1 + this.getOP() + num2 + " = " + (num1 + num2));
}
};
private String op;
private BasicOperation(String op) {
this.op = op;
}
public String getOP() {
return op;
}
}
//擴展一個冪運算
public enum ExtendedOperation implements Operation{
PLUS("^") {
@Override
public void calc(int num1, int num2) {
System.out.println(num1 + this.getOP() + num2 + " = " + Math.pow(num1, num2));
}
};
private String op;
private ExtendedOperation(String op) {
this.op = op;
}
public String getOP() {
return op;
}
}
只要API通過接口的方式操作,就可以允許客戶端擴展自己的操作方式。