您好,登錄后才能下訂單哦!
前言
在 Java 中, 枚舉, 也稱為枚舉類型, 其是一種特殊的數據類型, 它使得變量能夠稱為一組預定義的常量。 其目的是強制編譯時類型安全。
枚舉類更加直觀,類型安全。使用常量會有以下幾個缺陷:
1. 類型不安全。若一個方法中要求傳入季節這個參數,用常量的話,形參就是int類型,開發者傳入任意類型的int類型值就行,但是如果是枚舉類型的話,就只能傳入枚舉類中包含的對象。
2. 沒有命名空間。開發者要在命名的時候以SEASON_開頭,這樣另外一個開發者再看這段代碼的時候,才知道這四個常量分別代表季節。
因此, 在 Java 中, enum 是保留的關鍵字。
1. 枚舉的定義
在 Java 是在 JDK 1.4 時決定引入的, 其在 JDK 1.5 發布時正式發布的。
舉一個簡單的例子:以日常生活中的方向來定義, 因為其名稱, 方位等都是確定, 一提到大家就都知道。
1.1 傳統的非枚舉方法
如果不使用枚舉, 我們可能會這樣子定義
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; }
以上的定義也是可以達到定義的, 我們在使用時
@Test public void testDirection() { System.out.println(getDirectionName(Direction.EAST)); System.out.println(getDirectionName(5));// 也可以這樣調用 } 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"; } }
運行起來也沒問題。 但是, 我們就如同上面第二種調用方式一樣, 其實我們的方向就在 4 種范圍之內,但在調用的時候傳入不是方向的一個 int 類型的數據, 編譯器是不會檢查出來的。
1.2 枚舉方法
我們使用枚舉來實現上面的功能
定義
public enum DirectionEnum { EAST, WEST, NORTH, SOUTH }
測試
@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"; } }
以上只是一個舉的例子, 其實, 枚舉中可以很方便的獲取自己的名稱。
通過使用枚舉, 我們可以很方便的限制了傳入的參數, 如果傳入的參數不是我們指定的類型, 則就發生錯誤。
1.3 定義總結
以剛剛的代碼為例
public enum DirectionEnum { EAST, WEST, NORTH, SOUTH }
2 枚舉的本質
枚舉在編譯時, 編譯器會將其編譯為 Java 中 java.lang.Enum
的子類。
我們將上面的 DirectionEnum 進行反編譯, 可以獲得如下的代碼:
// 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 方法, 調用父類的 valueOf 方法 public static DirectionEnum valueOf(String name) { return (DirectionEnum)Enum.valueOf(cn/homejim/java/lang/DirectionEnum, name); } // 私有化構造函數, 正常情況下無法從外部進行初始化 private DirectionEnum(String s, int i) { super(s, 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 }); } }
通過以上反編譯的代碼, 可以發現以下幾個特點
2.1 繼承 java.lang.Enum
通過以上的反編譯, 我們知道了, java.lang.Enum
是所有枚舉類型的基類。查看其定義
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
可以看出來, java.lang.Enum 有如下幾個特征
因此, 相對應的, 枚舉類型也可以進行比較和序列化
2.2 final 類型
final 修飾, 說明枚舉類型是無法進行繼承的
2.3 枚舉常量本身就是該類的實例對象
可以看到, 我們定義的常量, 在類內部是以實例對象存在的, 并使用靜態代碼塊進行了實例化。
2.4 構造函數私有化
不能像正常的類一樣, 從外部 new 一個對象出來。
2.5 添加了 $values[] 變量及兩個方法
3 枚舉的一般使用
枚舉默認是有幾個方法的
3.1 類本身的方法
從前面我的分析, 我們得出, 類本身有兩個方法, 是編譯時添加的
3.1.1 values()
先看其源碼
public static DirectionEnum[] values() { return (DirectionEnum[])$VALUES.clone(); }
返回的是枚舉常量的克隆數組。
使用示例
@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)
該方法通過字符串獲取對應的枚舉常量
@Test public void testValueOf() { DirectionEnum east = DirectionEnum.valueOf("EAST"); System.out.println(east.ordinal());// 輸出0 }
3.2 繼承的方法
因為枚舉類型繼承于 java.lang.Enum
, 因此除了該類的私有方法, 其他方法都是可以使用的。
3.2.1 ordinal()
該方法返回的是枚舉實例的在定義時的順序, 類似于數組, 第一個實例該方法的返回值為 0。
在基于枚舉的復雜數據結構 EnumSet和EnumMap 中會用到該函數。
@Test public void testOrdinal() { System.out.println(DirectionEnum.EAST.ordinal());// 輸出 0 System.out.println(DirectionEnum.NORTH.ordinal()); // 輸出 2 }
3.2.2 compareTo()
該方法時實現的 Comparable 接口的, 其實現如下
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 來得出大于、小于還是等于。
@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()
獲取對應枚舉類型的 Class 對象
@Test public void testGetDeclaringClass() { System.out.println(DirectionEnum.WEST.getDeclaringClass()); // 輸出 class cn.homejim.java.lang.DirectionEnum }
2.3.5 equals
判斷指定對象與枚舉常量是否相同
@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 自定義構造函數
首先, 定義的構造函數可以是 private, 或不加修飾符
自定義構造函數
我們給每個方向加上一個角度
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; } }
測試
@Test public void testConstructor() { System.out.println(DirectionEnum.WEST.getAngle()); // 180 System.out.println(DirectionEnum.EAST.getAngle()); // 0 }
4.2 添加自定義的方法
以上的 getAngle 就是我們添加的自定義的方法
4.2.1 自定義具體方法
我們在枚舉類型內部加入如下具體方法
protected void move() { System.out.println("You are moving to " + this + " direction"); }
測試
@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中定義如下的抽象方法
abstract String onDirection();
定義完之后, 發現編譯器報錯了, 說我們需要實現這個方法
按要求實現
測試
@Test public void testAbstractMethod() { System.out.println(DirectionEnum.EAST.onDirection()); System.out.println(DirectionEnum.SOUTH.onDirection()); }
輸出
EAST direction 1
NORTH direction 333
也就是說抽象方法會強制要求每一個枚舉常量自己實現該方法。 通過提供不同的實現來達到不同的目的。
4.3 覆蓋父類方法
在父類 java.lang.Enum 中, 也就只有 toString() 是沒有使用 final 修飾啦, 要覆蓋也只能覆蓋該方法。 該方法的覆蓋相信大家很熟悉, 在此就不做過多的講解啦
4.4 實現接口
因為Java是單繼承的, 因此, Java中的枚舉因為已經繼承了 java.lang.Enum, 因此不能再繼承其他的類。
但Java是可以實現多個接口的, 因此 Java 中的枚舉也可以實現接口。
定義接口
public interface TestInterface { void doSomeThing(); }
實現接口
public enum DirectionEnum implements TestInterface{ // 其他代碼 public void doSomeThing() { System.out.println("doSomeThing Implement"); } // 其他代碼 }
測試
@Test public void testImplement() { DirectionEnum.WEST.doSomeThing(); // 輸出 doSomeThing Implement }
5 使用枚舉實現單例
該方法是在 《Effective Java》 提出的
public enum Singlton { INSTANCE; public void doOtherThing() { } }
使用枚舉的方式, 保證了序列化機制, 絕對防止多次序列化問題, 保證了線程的安全, 保證了單例。 同時, 防止了反射的問題。
該方法無論是創建還是調用, 都是很簡單。 《Effective Java》 對此的評價:
單元素的枚舉類型已經成為實現Singleton的最佳方法。
6 枚舉相關的集合類
java.util.EnumSet 和 java.util.EnumMap, 在此不進行過多的講述了。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。