本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連接 html
![]()
前面系列,咱們介紹了Java中表示和操做數據的基本數據類型、類和接口,本節探討Java中的枚舉類型。java
所謂枚舉,是一種特殊的數據,它的取值是有限的,能夠枚舉出來的,好比說一年就是有四季、一週有七天,雖然使用類也能夠處理這種數據,但枚舉類型更爲簡潔、安全和方便。編程
下面咱們就來介紹枚舉的使用,同時介紹其實現原理。數組
定義和使用基本的枚舉是比較簡單的,咱們來看個例子,爲表示衣服的尺寸,咱們定義一個枚舉類型Size,包括三個尺寸,小/中/大,代碼以下:安全
public enum Size {
SMALL, MEDIUM, LARGE
}
複製代碼
枚舉使用enum這個關鍵字來定義,Size包括三個值,分別表示小、中、大,值通常是大寫的字母,多個值之間以逗號分隔。枚舉類型能夠定義爲一個單獨的文件,也能夠定義在其餘類內部。微信
能夠這樣使用Size:網絡
Size size = Size.MEDIUM
複製代碼
Size size聲明瞭一個變量size,它的類型是Size,size=Size.MEDIUM將枚舉值MEDIUM賦值給size變量。ide
枚舉變量的toString方法返回其字面值,全部枚舉類型也都有一個name()方法,返回值與toString()同樣,例如:this
Size size = Size.SMALL;
System.out.println(size.toString());
System.out.println(size.name());
複製代碼
輸出都是SMALL。spa
枚舉變量可使用equals和==進行比較,結果是同樣的,例如:
Size size = Size.SMALL;
System.out.println(size==Size.SMALL);
System.out.println(size.equals(Size.SMALL));
System.out.println(size==Size.MEDIUM);
複製代碼
上面代碼的輸出結果爲三行,分別是true, true, false。
枚舉值是有順序的,能夠比較大小。枚舉類型都有一個方法int ordinal(),表示枚舉值在聲明時的順序,從0開始,例如,以下代碼輸出爲1:
Size size = Size.MEDIUM;
System.out.println(size.ordinal());
複製代碼
另外,枚舉類型都實現了Java API中的Comparable接口,均可以經過方法compareTo與其餘枚舉值進行比較,比較其實就是比較ordinal的大小,例如,以下代碼輸出爲-1,表示SMALL小於MEDIUM:
Size size = Size.SMALL;
System.out.println(size.compareTo(Size.MEDIUM));
複製代碼
枚舉變量能夠用於和其餘類型變量同樣的地方,如方法參數、類變量、實例變量等,枚舉還能夠用於switch語句,代碼以下所示:
static void onChosen(Size size){
switch(size){
case SMALL:
System.out.println("chosen small"); break;
case MEDIUM:
System.out.println("chosen medium"); break;
case LARGE:
System.out.println("chosen large"); break;
}
}
複製代碼
在switch語句內部,枚舉值不能帶枚舉類型前綴,例如,直接使用SMALL,不能使用Size.SMALL。
枚舉類型都有一個靜態的valueOf(String)方法,能夠返回字符串對應的枚舉值,例如,如下代碼輸出爲true:
System.out.println(Size.SMALL==Size.valueOf("SMALL"));
複製代碼
枚舉類型也都有一個靜態的values方法,返回一個包括全部枚舉值的數組,順序與聲明時的順序一致,例如:
for(Size size : Size.values()){
System.out.println(size);
}
複製代碼
屏幕輸出爲三行,分別是SMALL, MEDIUM, LARGE。
Java是從JDK 5纔開始支持枚舉的,在此以前,通常是在類中定義靜態整形變量來實現相似功能,代碼以下所示:
class Size {
public static final int SMALL = 0;
public static final int MEDIUM = 1;
public static final int LARGE = 2;
}
複製代碼
枚舉的好處是比較明顯的:
枚舉類型實際上會被Java編譯器轉換爲一個對應的類,這個類繼承了Java API中的java.lang.Enum類。
Enum類有兩個實例變量name和ordinal,在構造方法中須要傳遞,name(), toString(), ordinal(), compareTo(), equals()方法都是由Enum類根據其實例變量name和ordinal實現的。
values和valueOf方法是編譯器給每一個枚舉類型自動添加的,上面的枚舉類型Size轉換後的普通類的代碼大概以下所示:
public final class Size extends Enum<Size> {
public static final Size SMALL = new Size("SMALL",0);
public static final Size MEDIUM = new Size("MEDIUM",1);
public static final Size LARGE = new Size("LARGE",2);
private static Size[] VALUES =
new Size[]{SMALL,MEDIUM,LARGE};
private Size(String name, int ordinal){
super(name, ordinal);
}
public static Size[] values(){
Size[] values = new Size[VALUES.length];
System.arraycopy(VALUES, 0,
values, 0, VALUES.length);
return values;
}
public static Size valueOf(String name){
return Enum.valueOf(Size.class, name);
}
}
複製代碼
解釋幾點:
通常枚舉變量會被轉換爲對應的類變量,在switch語句中,枚舉值會被轉換爲其對應的ordinal值。
能夠看出,枚舉類型本質上也是類,但因爲編譯器自動作了不少事情,它的使用也就更爲簡潔、安全和方便。
以上枚舉用法是最簡單的,實際中枚舉常常會有關聯的實例變量和方法,好比說,上面的Size例子,每一個枚舉值可能有關聯的縮寫和中文名稱,可能須要靜態方法根據縮寫返回對應的枚舉值,修改後的Size代碼以下所示:
public enum Size {
SMALL("S","小號"),
MEDIUM("M","中號"),
LARGE("L","大號");
private String abbr;
private String title;
private Size(String abbr, String title){
this.abbr = abbr;
this.title = title;
}
public String getAbbr() {
return abbr;
}
public String getTitle() {
return title;
}
public static Size fromAbbr(String abbr){
for(Size size : Size.values()){
if(size.getAbbr().equals(abbr)){
return size;
}
}
return null;
}
}
複製代碼
以上代碼定義了兩個實例變量abbr和title,以及對應的get方法,分別表示縮寫和中文名稱,定義了一個私有構造方法,接受縮寫和中文名稱,每一個枚舉值在定義的時候都傳遞了對應的值,同時定義了一個靜態方法fromAbbr根據縮寫返回對應的枚舉值。
須要說明的是,枚舉值的定義須要放在最上面,枚舉值寫完以後,要以分號(;)結尾,而後才能寫其餘代碼。
這個枚舉定義的使用與其餘類相似,好比說:
Size s = Size.MEDIUM;
System.out.println(s.getAbbr());
s = Size.fromAbbr("L");
System.out.println(s.getTitle());
複製代碼
以上代碼分別輸出: M, 大號
加了實例變量和方法後,枚舉轉換後的類與上面的相似,只是增長了對應的變量和方法,修改了構造方法,代碼不一樣之處大概以下所示:
public final class Size extends Enum<Size> {
public static final Size SMALL =
new Size("SMALL",0, "S", "小號");
public static final Size MEDIUM =
new Size("MEDIUM",1,"M","中號");
public static final Size LARGE =
new Size("LARGE",2,"L","大號");
private String abbr;
private String title;
private Size(String name, int ordinal, String abbr, String title){
super(name, ordinal);
this.abbr = abbr;
this.title = title;
}
//... 其餘代碼
}
複製代碼
每一個枚舉值常常有一個關聯的標示(id),一般用int整數表示,使用整數能夠節約存儲空間,減小網絡傳輸。一個天然的想法是使用枚舉中自帶的ordinal值,但ordinal並非一個好的選擇。
爲何呢?由於ordinal的值會隨着枚舉值在定義中的位置變化而變化,但通常來講,咱們但願id值和枚舉值的關係保持不變,尤爲是表示枚舉值的id已經保存在了不少地方的時候。
好比說,上面的Size例子,Size.SMALL的ordinal的值爲0,咱們但願0表示的就是Size.SMALL的,但若是咱們增長一個表示超小的值XSMALL呢?
public enum Size {
XSMALL, SMALL, MEDIUM, LARGE
}
複製代碼
這時,0就表示XSMALL了。
因此,通常是增長一個實例變量表示id,使用實例變量的另外一個好處是,id能夠本身定義。好比說,Size例子能夠寫爲:
public enum Size {
XSMALL(10), SMALL(20), MEDIUM(30), LARGE(40);
private int id;
private Size(int id){
this.id = id;
}
public int getId() {
return id;
}
}
複製代碼
枚舉還有一些高級用法,好比說,每一個枚舉值能夠有關聯的類定義體,枚舉類型能夠聲明抽象方法,每一個枚舉值中能夠實現該方法,也能夠重寫枚舉類型的其餘方法。
好比說,咱們看改後的Size代碼(這個代碼實際意義不大,主要展現語法):
public enum Size {
SMALL {
@Override
public void onChosen() {
System.out.println("chosen small");
}
},MEDIUM {
@Override
public void onChosen() {
System.out.println("chosen medium");
}
},LARGE {
@Override
public void onChosen() {
System.out.println("chosen large");
}
};
public abstract void onChosen();
}
複製代碼
Size枚舉類型定義了onChosen抽象方法,表示選擇了該尺寸後執行的代碼,每一個枚舉值後面都有一個類定義體{},都重寫了onChosen方法。
這種寫法有什麼好處呢?若是每一個或部分枚舉值有一些特定的行爲,使用這種寫法比較簡潔。對於這個例子,上面咱們介紹了其對應的switch語句,在switch語句中根據size的值執行不一樣的代碼。
switch的缺陷是,定義swich的代碼和定義枚舉類型的代碼可能不在一塊兒,若是新增了枚舉值,應該須要一樣修改switch代碼,但可能會忘記,而若是使用抽象方法,則不可能忘記,在定義枚舉值的同時,編譯器會強迫同時定義相關行爲代碼。因此,若是行爲代碼和枚舉值是密切相關的,使用以上寫法能夠更爲簡潔、安全、容易維護。
這種寫法內部是怎麼實現的呢?每一個枚舉值都會生成一個類,這個類繼承了枚舉類型對應的類,而後再加上值特定的類定義體代碼,枚舉值會變成這個子類的對象,具體代碼咱們就不贅述了。
枚舉還有一些其餘高級用法,好比說,枚舉能夠實現接口,也能夠在接口中定義枚舉,使用相對較少,本文就不介紹了。
本節介紹了枚舉類型,介紹了基礎用法、典型場景及高級用法,不只介紹瞭如何使用,還介紹了實現原理,對於枚舉類型的數據,雖然直接使用類也能夠處理,但枚舉類型更爲簡潔、安全和方便。
咱們以前提到過異常,但並未深刻討論,讓咱們下節來探討。
未完待續,查看最新文章,敬請關注微信公衆號「老馬說編程」(掃描下方二維碼),深刻淺出,老馬和你一塊兒探索Java編程及計算機技術的本質。用心原創,保留全部版權。