天天看點

Java開發筆記(五十六)利用枚舉類型實作進階常量

前面介紹了聯合利用final和static可實作常量的定義,該方式用于簡單的常量倒還湊合,要是用于複雜的、安全性高的常量,那就力不從心了。例如以下幾種情況,final結合static的方式便缺乏應對之策:

1、雖然常量的名稱以大寫字母拼寫,但是對應的取值基本為1、2、3之類的整數,如果把1、2、3直接寫在調用的代碼裡面,豈不是渾水摸魚頂替了現有的常量蒙混過關?

2、代碼可以從常量名推出對應的常量值,可是反過來并不能從常量值推出對應的常量名,開發者曉得不代表程式也曉得。

3、每個常量隻有唯一的數值表達,無法表示更豐富的涵義。比如星期一這個常量,可能包括數字“1”、英文單詞“Monday”、中文詞語“星期一”這些資訊組合,然而final聯合static的方式隻能表達其中一個資訊。

聽起來似乎言之有理,可是不用整型常量的話,還有什麼常量類型能派上用場呢?其實Java語言在設計之初就考慮到了這種情況,在之前的學習當中,已經出現過類似的處理方案。早在介紹本地日期類型LocalDate的時候,提到擷取目前月份的辦法是調用日期執行個體的getMonthValue方法,為啥這裡不是調用getMonth方法?原來getMonth方法傳回的并非整型數值,而是一個Month類型的月份執行個體,它屬于枚舉類型。調用該執行個體的getValue方法,得到的才是月份數字;調用該執行個體的name方法,可得到大寫英文月份的英文單詞。先來看看以下的一段月份測試代碼:

// 示範Month類型的調用方式。注意,Month類型是Java自帶的一種枚舉類型
private static void testMonth() {
	LocalDate date = LocalDate.now();
	Month month = date.getMonth();
	System.out.println("month number="+month.getValue()+", month name="+month.name());
}
           

運作以上的測試代碼,觀察到如下的日志文本:

month number=12, month name=DECEMBER
           

根據結果發現getValue方法傳回了12,且name方法傳回了DECEMBER。從中可見Month類型既包含了月份的數字資訊,也包括了月份的英文單詞,這正是枚舉類型相對于普通常量的優勢。

所謂枚舉,指的是某些同類型常量的有限集合。Java不但提供了Month這種枚舉類型,而且允許程式員自己定義新的枚舉類型,如同定義類那樣。不同的是,類定義使用class來辨別,而枚舉類型使用enum來辨別。最簡單的枚舉定義隻需一個名稱清單,就像以下代碼這般:

//示範枚舉類型的簡單定義
public enum Season {
	// 幾個枚舉變量之間以逗号分隔
	SPRING, SUMMER, AUTUMN, WINTER
}
           

以上代碼定義了一種季節枚舉Season,它包含了春天SPRING、夏天SUMMER、秋天AUTUMN、冬天WINTER這四個枚舉項。四個枚舉項既是常量,又都屬于Season類型,外部通路它們的格式為“Season.枚舉項的名稱”。下面是外部通路季節枚舉項的代碼例子:

// 示範簡單枚舉類型的調用方式
private static void testEnum() {
	Season spring = Season.SPRING; // 聲明一個春天的季節執行個體
	Season summer = Season.SUMMER; // 聲明一個夏天的季節執行個體
	Season autumn = Season.AUTUMN; // 聲明一個秋天的季節執行個體
	Season winter = Season.WINTER; // 聲明一個冬天的季節執行個體
	// 枚舉類型提供的通用方法主要有兩個,
	// 其中ordinal方法可獲得該枚舉的序号,toString可獲得該枚舉的字段名稱
	System.out.println("spring number="+spring.ordinal()+", name="+spring.toString());
	System.out.println("summer number="+summer.ordinal()+", name="+summer.toString());
	System.out.println("autumn number="+autumn.ordinal()+", name="+autumn.toString());
	System.out.println("winter number="+winter.ordinal()+", name="+winter.toString());
}
           

運作上面的測試代碼,輸出下列的日志資訊:

spring number=0, name=SPRING
summer number=1, name=SUMMER
autumn number=2, name=AUTUMN
winter number=3, name=WINTER
           

結合代碼和日志結果,可知枚舉項的ordinal方法傳回了該枚舉所處的序号,toString方法傳回了該枚舉的常量名稱。由于ordinal方法和toString方法是枚舉類型enum自帶的保留方法,是以無需開發者顯式定義即可拿來調用。然而這兩個方法畢竟是系統提供的,無法滿足豐富多變的個性要求,譬如下列兩點需求,簡單的枚舉類型就無法實作:

1、枚舉項的預設序号從0開始計數,但現實生活中很多組合是從1開始計數的。例如一月份對應的數字是1,星期一對應的數字也是1,諸如此類。

2、枚舉項的預設名稱取的是枚舉定義裡的清單項名稱,但往往更需要中文名稱。例如界面上希望展示“春天”而非“SPRING”,希望展示“夏天”而非“SUMMER”,等等。

既然枚舉的預設序号與預設名稱時常不符合實際情況,這勢必要求開發者額外定義新的序号和新的名稱。假如說給某個類定義新的屬性,那真是易如反掌,可現在待處理的是枚舉類型而不是類耶。其實枚舉類型enum本來就源自類class,故而完全可以把枚舉當作類一樣來定義,也就是說,枚舉允許定義自己的成員屬性、自己的成員方法,乃至自己的構造方法。于是重新定義一個季節枚舉,在新的枚舉定義中添加序号與名稱這兩個屬性,及其對應的get方法,并補充包含初始化指派的構造方法。特别注意要在枚舉項清單中把每個枚舉項都換成攜帶構造方法的枚舉聲明,表示該枚舉項是由指定構造方法生成的,重寫後的季節枚舉定義代碼示例如下:

//示範枚舉類型的擴充定義
public enum SeasonCn {
	// 在定義枚舉變量的同時,調用該枚舉變量的構造方法
	SPRING(1,"春天"), SUMMER(2,"夏天"), AUTUMN(3,"秋天"), WINTER(4,"冬天");

	private int value; // 定義季節的阿拉伯數字
	private String name; // 定義季節的中文名稱字段

	// 在構造方法中傳入該季節的阿拉伯數字和中文名稱
	private SeasonCn(int value, String name) {
		this.value = value;
		this.name = name;
	}

	// 擷取季節的阿拉伯數字
	public int getValue() {
		return this.value;
	}

	// 擷取季節的中文名稱
	public String getName() {
		return this.name;
	}
}
           

根據新的枚舉定義代碼,枚舉項的序号數值與中文名稱如願換了過來。接着輪到外部調用新的枚舉類型,大緻流程保持不變,隻需将原來的ordinal方法替換為getValue方法,将原來的toString方法替換為getName方法。修改之後的調用代碼如下所示:

// 示範擴充枚舉類型的調用方式
private static void testEnumCn() {
	SeasonCn spring = SeasonCn.SPRING; // 聲明一個春天的季節執行個體
	SeasonCn summer = SeasonCn.SUMMER; // 聲明一個夏天的季節執行個體
	SeasonCn autumn = SeasonCn.AUTUMN; // 聲明一個秋天的季節執行個體
	SeasonCn winter = SeasonCn.WINTER; // 聲明一個冬天的季節執行個體
	// 通過擴充而來的getName方法,可獲得該枚舉預先設定的中文名稱
	System.out.println("spring number="+spring.getValue()+", name="+spring.getName());
	System.out.println("summer number="+summer.getValue()+", name="+summer.getName());
	System.out.println("autumn number="+autumn.getValue()+", name="+autumn.getName());
	System.out.println("winter number="+winter.getValue()+", name="+winter.getName());
}
           

運作上述的調用代碼,得到以下的日志結果:

spring number=1, name=春天
summer number=2, name=夏天
autumn number=3, name=秋天
winter number=4, name=冬天
           

由此可見,經過重新編寫的SeasonCn枚舉,順利實作了個性化定制序号和名稱的目标。

更多Java技術文章參見《Java開發筆記(序)章節目錄》