前言
在 java 中, 枚舉, 也稱為枚舉類型, 其是一種特殊的數(shù)據(jù)類型, 它使得變量能夠稱為一組預(yù)定義的常量。 其目的是強制編譯時類型安全。
枚舉類更加直觀,類型安全。使用常量會有以下幾個缺陷:
1. 類型不安全。若一個方法中要求傳入季節(jié)這個參數(shù),用常量的話,形參就是int類型,開發(fā)者傳入任意類型的int類型值就行,但是如果是枚舉類型的話,就只能傳入枚舉類中包含的對象。
2. 沒有命名空間。開發(fā)者要在命名的時候以season_開頭,這樣另外一個開發(fā)者再看這段代碼的時候,才知道這四個常量分別代表季節(jié)。
因此, 在 java 中, enum 是保留的關(guān)鍵字。
1. 枚舉的定義
在 java 是在 jdk 1.4 時決定引入的, 其在 jdk 1.5 發(fā)布時正式發(fā)布的。
舉一個簡單的例子:以日常生活中的方向來定義, 因為其名稱, 方位等都是確定, 一提到大家就都知道。
1.1 傳統(tǒng)的非枚舉方法
如果不使用枚舉, 我們可能會這樣子定義
1
2
3
4
5
6
7
8
9
10
|
public class direction { public static final int east = 0 ; public static final int west = 1 ; public static final int south = 2 ; public static final int north = 3 ; } |
以上的定義也是可以達(dá)到定義的, 我們在使用時
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@test public void testdirection() { system.out.println(getdirectionname(direction.east)); system.out.println(getdirectionname( 5 )); // 也可以這樣調(diào)用 } public string getdirectionname( int type) { switch (type) { case direction.east: return "east" ; case direction.west: return "west" ; case direction.south: return "south" ; case direction.north: return "north" ; default : return "unknow" ; } } |
運行起來也沒問題。 但是, 我們就如同上面第二種調(diào)用方式一樣, 其實我們的方向就在 4 種范圍之內(nèi),但在調(diào)用的時候傳入不是方向的一個 int 類型的數(shù)據(jù), 編譯器是不會檢查出來的。
1.2 枚舉方法
我們使用枚舉來實現(xiàn)上面的功能
定義
1
2
3
|
public enum directionenum { east, west, north, south } |
測試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@test public void testdirectionenum() { system.out.println(getdirectionname(directionenum.east)); // system.out.println(getdirectionname(5));// 編譯錯誤 } public string getdirectionname(directionenum direction) { switch (direction) { case east: return "east" ; case west: return "west" ; case south: return "south" ; case north: return "north" ; default : return "unknow" ; } } |
以上只是一個舉的例子, 其實, 枚舉中可以很方便的獲取自己的名稱。
通過使用枚舉, 我們可以很方便的限制了傳入的參數(shù), 如果傳入的參數(shù)不是我們指定的類型, 則就發(fā)生錯誤。
1.3 定義總結(jié)
以剛剛的代碼為例
1
2
3
|
public enum directionenum { east, west, north, south } |
- 枚舉類型的定義跟類一樣, 只是需要將 class 替換為 enum
- 枚舉名稱與類的名稱遵循一樣的慣例來定義
- 枚舉值由于是常量, 一般推薦全部是大寫字母
- 多個枚舉值之間使用逗號分隔開
- 最好是在編譯或設(shè)計時就知道值的所有類型, 比如上面的方向, 當(dāng)然后面也可以增加
2 枚舉的本質(zhì)
枚舉在編譯時, 編譯器會將其編譯為 java 中 java.lang.enum
的子類。
我們將上面的 directionenum 進行反編譯, 可以獲得如下的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
// final:無法繼承 public final class directionenum extends enum { // 在之前定義的實例 public static final directionenum east; public static final directionenum west; public static final directionenum north; public static final directionenum south; private static final directionenum $values[]; // 編譯器添加的 values() 方法 public static directionenum[] values() { return (directionenum[])$values.clone(); } // 編譯器添加的 valueof 方法, 調(diào)用父類的 valueof 方法 public static directionenum valueof(string name) { return (directionenum) enum .valueof(cn/homejim/java/lang/directionenum, name); } // 私有化構(gòu)造函數(shù), 正常情況下無法從外部進行初始化 private directionenum(string s, int i) { super (s, i); } // 靜態(tài)代碼塊初始化枚舉實例 static { east = new directionenum( "east" , 0 ); west = new directionenum( "west" , 1 ); north = new directionenum( "north" , 2 ); south = new directionenum( "south" , 3 ); $values = ( new directionenum[] { east, west, north, south }); } } |
通過以上反編譯的代碼, 可以發(fā)現(xiàn)以下幾個特點
2.1 繼承 java.lang.enum
通過以上的反編譯, 我們知道了, java.lang.enum
是所有枚舉類型的基類。查看其定義
1
2
|
public abstract class enum <e extends enum <e>> implements comparable<e>, serializable { |
可以看出來, java.lang.enum 有如下幾個特征
- 抽象類, 無法實例化
- 實現(xiàn)了 comparable 接口, 可以進行比較
- 實現(xiàn)了 serializable 接口, 可進行序列化
因此, 相對應(yīng)的, 枚舉類型也可以進行比較和序列化
2.2 final 類型
final 修飾, 說明枚舉類型是無法進行繼承的
2.3 枚舉常量本身就是該類的實例對象
可以看到, 我們定義的常量, 在類內(nèi)部是以實例對象存在的, 并使用靜態(tài)代碼塊進行了實例化。
2.4 構(gòu)造函數(shù)私有化
不能像正常的類一樣, 從外部 new 一個對象出來。
2.5 添加了 $values[] 變量及兩個方法
- $values[]: 一個類型為枚舉類本身的數(shù)組, 存儲了所有的示例類型
- values() : 獲取以上所有實例變量的克隆值
- valueof(): 通過該方法可以通過名稱獲得對應(yīng)的枚舉常量
3 枚舉的一般使用
枚舉默認(rèn)是有幾個方法的
3.1 類本身的方法
從前面我的分析, 我們得出, 類本身有兩個方法, 是編譯時添加的
3.1.1 values()
先看其源碼
1
2
3
|
public static directionenum[] values() { return (directionenum[])$values.clone(); } |
返回的是枚舉常量的克隆數(shù)組。
使用示例
1
2
3
4
5
6
7
8
|
@test public void testvalus() { directionenum[] values = directionenum.values(); for (directionenum direction: values) { system.out.println(direction); } } |
輸出
east
west
north
south
3.1.2 valueof(string)
該方法通過字符串獲取對應(yīng)的枚舉常量
1
2
3
4
5
|
@test public void testvalueof() { directionenum east = directionenum.valueof( "east" ); system.out.println(east.ordinal()); // 輸出0 } |
3.2 繼承的方法
因為枚舉類型繼承于 java.lang.enum
, 因此除了該類的私有方法, 其他方法都是可以使用的。
3.2.1 ordinal()
該方法返回的是枚舉實例的在定義時的順序, 類似于數(shù)組, 第一個實例該方法的返回值為 0。
在基于枚舉的復(fù)雜數(shù)據(jù)結(jié)構(gòu) enumset和enummap 中會用到該函數(shù)。
1
2
3
4
5
|
@test public void testordinal() { system.out.println(directionenum.east.ordinal()); // 輸出 0 system.out.println(directionenum.north.ordinal()); // 輸出 2 } |
3.2.2 compareto()
該方法時實現(xiàn)的 comparable 接口的, 其實現(xiàn)如下
1
2
3
4
5
6
7
8
|
public final int compareto(e o) { enum <?> other = ( enum <?>)o; enum <e> self = this ; if (self.getclass() != other.getclass() && // optimization self.getdeclaringclass() != other.getdeclaringclass()) throw new classcastexception(); return self.ordinal - other.ordinal; } |
首先, 需要枚舉類型是同一種類型, 然后比較他們的 ordinal 來得出大于、小于還是等于。
1
2
3
4
5
6
|
@test public void testcompareto() { system.out.println(directionenum.east.compareto(directionenum.east) == 0 ); // true system.out.println(directionenum.west.compareto(directionenum.east) > 0 ); // true system.out.println(directionenum.west.compareto(directionenum.south) < 0 ); // true } |
3.2.3 name() 和 tostring()
該兩個方法都是返回枚舉常量的名稱。 但是, name()
方法時 final 類型, 是不能被覆蓋的! 而 tostring 可以被覆蓋。
3.2.4 getdeclaringclass()
獲取對應(yīng)枚舉類型的 class 對象
1
2
3
4
5
|
@test public void testgetdeclaringclass() { system.out.println(directionenum.west.getdeclaringclass()); // 輸出 class cn.homejim.java.lang.directionenum } |
2.3.5 equals
判斷指定對象與枚舉常量是否相同
1
2
3
4
5
|
@test public void testequals() { system.out.println(directionenum.west.equals(directionenum.east)); // false system.out.println(directionenum.west.equals(directionenum.west)); // true } |
4 枚舉類型進階
枚舉類型通過反編譯我們知道, 其實也是一個類(只不過這個類比較特殊, 加了一些限制), 那么, 在類上能做的一些事情對其也是可以做的。 但是, 個別的可能會有限制(方向吧, 編譯器會提醒我們的)
4.1 自定義構(gòu)造函數(shù)
首先, 定義的構(gòu)造函數(shù)可以是 private, 或不加修飾符
自定義構(gòu)造函數(shù)
我們給每個方向加上一個角度
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public enum directionenum { east( 0 ), west( 180 ), north( 90 ), south( 270 ); private int angle; directionenum( int angle) { this .angle = angle; } public int getangle() { return angle; } } |
測試
1
2
3
4
5
|
@test public void testconstructor() { system.out.println(directionenum.west.getangle()); // 180 system.out.println(directionenum.east.getangle()); // 0 } |
4.2 添加自定義的方法
以上的 getangle 就是我們添加的自定義的方法
4.2.1 自定義具體方法
我們在枚舉類型內(nèi)部加入如下具體方法
1
2
3
|
protected void move() { system.out.println( "you are moving to " + this + " direction" ); } |
測試
1
2
3
4
5
|
@test public void testconcretemethod() { directionenum.west.move(); directionenum.north.move(); } |
輸出
you are moving to west direction
you are moving to north direction
4.2.2 在枚舉中定義抽象方法
在枚舉類型中, 也是可以定義 abstract 方法的
我們在directinenum中定義如下的抽象方法
1
|
abstract string ondirection(); |
定義完之后, 發(fā)現(xiàn)編譯器報錯了, 說我們需要實現(xiàn)這個方法
按要求實現(xiàn)
測試
1
2
3
4
5
|
@test public void testabstractmethod() { system.out.println(directionenum.east.ondirection()); system.out.println(directionenum.south.ondirection()); } |
輸出
east direction 1
north direction 333
也就是說抽象方法會強制要求每一個枚舉常量自己實現(xiàn)該方法。 通過提供不同的實現(xiàn)來達(dá)到不同的目的。
4.3 覆蓋父類方法
在父類 java.lang.enum 中, 也就只有 tostring() 是沒有使用 final 修飾啦, 要覆蓋也只能覆蓋該方法。 該方法的覆蓋相信大家很熟悉, 在此就不做過多的講解啦
4.4 實現(xiàn)接口
因為java是單繼承的, 因此, java中的枚舉因為已經(jīng)繼承了 java.lang.enum, 因此不能再繼承其他的類。
但java是可以實現(xiàn)多個接口的, 因此 java 中的枚舉也可以實現(xiàn)接口。
定義接口
1
2
3
|
public interface testinterface { void dosomething(); } |
實現(xiàn)接口
1
2
3
4
5
6
7
|
public enum directionenum implements testinterface{ // 其他代碼 public void dosomething() { system.out.println( "dosomething implement" ); } // 其他代碼 } |
測試
1
2
3
4
|
@test public void testimplement() { directionenum.west.dosomething(); // 輸出 dosomething implement } |
5 使用枚舉實現(xiàn)單例
該方法是在 《effective java》 提出的
1
2
3
4
5
6
7
|
public enum singlton { instance; public void dootherthing() { } } |
使用枚舉的方式, 保證了序列化機制, 絕對防止多次序列化問題, 保證了線程的安全, 保證了單例。 同時, 防止了反射的問題。
該方法無論是創(chuàng)建還是調(diào)用, 都是很簡單。 《effective java》 對此的評價:
單元素的枚舉類型已經(jīng)成為實現(xiàn)singleton的最佳方法。
6 枚舉相關(guān)的集合類
java.util.enumset 和 java.util.enummap, 在此不進行過多的講述了。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對服務(wù)器之家的支持。
原文鏈接:https://www.cnblogs.com/homejim/p/10056701.html