Java列舉:小小enum,優雅而乾淨

沉默王二發表於2019-03-15

《Java程式設計思想》中有這麼一句話:“有時恰恰因為它,你才能夠‘優雅而乾淨’地解決問題”——這句話說的是誰呢?就是本篇的主角——列舉(Enum)——大家鼓掌了。

在之前很長時間一段時間裡,都不怎麼用列舉,因為總感覺它沒什麼用處——這其實就是“自我認知”的短見。當一個人一直蹲在自己的深井裡而不敢跳出來的話,那他真的只能看到井口那麼大點的天空

隨著時間的推移,我做的專案越來越多,和列舉見面的機會也越來越多,於是我就漸漸地對它越來越有興趣,研究得多了,才發現原來列舉如此的優秀。

1)列舉的常規用法

一個精簡的列舉非常的乾淨優雅,見下例。

public enum Chenmo {
	WANGER, WANGSAN, WANGSI
}
複製程式碼

我們為沉默列舉建立了三個值,分別是王二、王三、王四。這段程式碼實際上呼叫了3次Enum(String name, int ordinal)(ordinal單詞的意思為順序),也就是:

new Enum<Chenmo>("WANGER", 0);
new Enum<Chenmo>("WANGSAN", 1);
new Enum<Chenmo>("WANGSI", 2);
複製程式碼

我們來遍歷輸出一下列舉:

for (Chenmo e : Chenmo.values()) {
    System.out.println(e);
}
//輸出
//WANGER
//WANGSAN
//WANGSI
複製程式碼

2)作為switch的判斷條件

使用列舉作為switch語句判斷條件能讓我們的程式碼可讀性更強,示例如下。

Chenmo key = Chenmo.WANGER;
switch (key) {
case WANGSI:
	System.out.println("今天我送出一個CSDN大滑鼠墊");
	break;
case WANGSAN:
	System.out.println("今天我被坑一個CSDN學院年卡");
	break;
default:
	System.out.println("今天我一邊高興,一邊失落");
	break;
}
複製程式碼

在通過case關鍵字判斷的時候,可以直接使用列舉值,非常簡潔。另外,在編譯期間限定型別,可以有效的避免越界的情況——字串常量型別在作為switch判斷條件的時候很容易因為誤寫而發生越界問題。

3)列舉實現單例

《Effective Java》一書中對使用列舉實現單例的方式推崇備至:

使用列舉實現單例的方法雖然還沒有廣泛採用,但是單元素的列舉型別已經成為實現Singleton的最佳方法。

我覺得“雖然還沒有廣泛採用”幾個字可以去掉了,時至今日,大家應該都知道:使用列舉實現單例是一種非常好的方式。

先來看“雙重校驗鎖”實現的單例:

public class SingleTon2 {

	 // 私有化構造方法
	private SingleTon2() {
	};

	private static volatile SingleTon2 singleTon = null;

	public static SingleTon2 getInstance() {

		// 第一次校驗
		if (singleTon == null) {
			synchronized (SingleTon2.class) {
				// 第二次校驗
				if (singleTon == null) {
					singleTon = new SingleTon2();
				}
			}
		}
		return singleTon;
	}
}
複製程式碼

再來看列舉實現的單例:

public enum SingleTon {

	 INSTANCE;
	
	public void method() {
		System.out.println("我很快樂!");
	}
}
複製程式碼

不比不知道,一比嚇一跳啊!列舉方式的單例簡單到爆——為了不至於看起來太過精簡,我還加了一個輸出“我很快樂”的方法。

列舉實現的單例可輕鬆地解決兩個問題:

①、執行緒安全問題。因為Java虛擬機器在載入列舉類的時候,會使用ClassLoader的loadClass方法,這個方法使用了同步程式碼塊來保證執行緒安全。

②、避免反序列化破壞單例。因為列舉的反序列化並不通過反射實現。

4)列舉可與資料庫互動

我們可以配合Mybatis將資料庫欄位轉換為列舉型別。現在假設有一個資料庫欄位check_type的型別如下:

`check_type` int(1) DEFAULT NULL COMMENT '檢查型別(1:未通過、2:通過)',
複製程式碼

它對應的列舉型別為CheckType,程式碼如下:

public enum CheckType {
	NO_PASS(0, "未通過"), PASS(1, "通過");
	private int key;

	private String text;

	private CheckType(int key, String text) {
		this.key = key;
		this.text = text;
	}

	public int getKey() {
		return key;
	}

	public String getText() {
		return text;
	}

	private static HashMap<Integer,CheckType> map = new HashMap<Integer,CheckType>();
	static {
		for(CheckType d : CheckType.values()){
			map.put(d.key, d);
		}
	}
	
	public static CheckType parse(Integer index) {
		if(map.containsKey(index)){
			return map.get(index);
		}
		return null;
	}
}
複製程式碼

CheckType列舉類比我們剛開始見到的那個Chenmo列舉類要複雜一些。

第一,CheckType新新增了構造方法,還有兩個欄位,key為int型,text為String型。

第二,CheckType中有一個public static CheckType parse(Integer index)方法,可將一個Integer通過key的匹配轉化為列舉型別。

那麼現在,我們可以在Mybatis的配置檔案中使用typeHandler將資料庫欄位轉化為列舉型別。

<resultMap id="CheckLog" type="com.entity.CheckLog">
  <id property="id" column="id"/>
  <result property="checkType" column="check_type" typeHandler="com.CheckTypeHandler"></result>
</resultMap>
複製程式碼

其中checkType欄位對應的類如下:

public class CheckLog implements Serializable {

    private String id;
    private CheckType checkType;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public CheckType getCheckType() {
        return checkType;
    }

    public void setCheckType(CheckType checkType) {
        this.checkType = checkType;
    }
}
複製程式碼

CheckTypeHandler轉換器的類原始碼如下:

public class CheckTypeHandler extends BaseTypeHandler<CheckType> {

	@Override
	public CheckType getNullableResult(ResultSet rs, String index) throws SQLException {
		return CheckType.parse(rs.getInt(index));
	}

	@Override
	public CheckType getNullableResult(ResultSet rs, int index) throws SQLException {
		return CheckType.parse(rs.getInt(index));
	}

	@Override
	public CheckType getNullableResult(CallableStatement cs, int index) throws SQLException {
		return CheckType.parse(cs.getInt(index));
	}

	@Override
	public void setNonNullParameter(PreparedStatement ps, int index, CheckType val, JdbcType arg3) throws SQLException {
		ps.setInt(index, val.getKey());
	}
}
複製程式碼

CheckTypeHandler 的核心功能就是呼叫CheckType列舉類的parse()方法對資料庫欄位進行轉換。

5)列舉會比靜態常量更消耗記憶體嗎?

說完列舉最常用的4個知識點後,我們來討論一下“列舉會比靜態常量更消耗記憶體嗎?”這個話題——知乎上有人問這樣的問題,還有很多人蔘與回答。

按我的理解,問這個問題的人就好像是在問“0.000,001”比“0.000,000,99”大嗎?你說是嗎?


PS:歡迎關注公眾號「沉默王二」,回覆關鍵字「0」獲取二哥精心為你準備的珍藏版電子書大禮包。

相關文章