Java枚舉使用方式和注意事項

枚舉類型 是一組固定常量組成合法值的類型,比如一年中的季節,一週中的星期。

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.EnumEnum實現了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通過接口的方式操作,就可以允許客戶端擴展自己的操作方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章