列舉是JDK1.5引入的新特性。被enum
關鍵字修飾的類就是一個列舉類。
關於列舉,阿里巴巴開發手冊有這樣兩條建議:
- 列舉類名帶上 Enum 字尾,列舉成員名稱需要全大寫,單詞間用下劃線隔開。
- 如果變數值僅在一個固定範圍內變化用 enum 型別來定義。
一 列舉類有哪些特點
建立一個ColorEnum
的列舉類,通過編譯,再反編譯看看它發生了哪些變化。
public enum ColorEnum {
RED,GREEN,BULE;
}
使用命令javac ColorEnum.java
進行編譯生成class檔案,然後再用命令javap -p ColorEnum.class
進行反編譯。
去掉包名,反編譯後的內容如下:
public final class ColorEnum extends Enum{
public static final ColorEnum GREEN;
public static final ColorEnum BULE;
private static final ColorEnum[] $VALUES;
public static ColorEnum[] values();
public static ColorEnum valueOf(java.lang.String);
private ColorEnum();
static {};
}
- 列舉類被
final
修飾,因此列舉類不能被繼承; - 列舉類預設繼承了
Enum
類,java不支援多繼承,因此列舉類不能繼承其他類; - 列舉類的構造器是
private
修飾的,因此其他類不能通過構造器來獲取物件; - 列舉類的成員變數是
static
修飾的,可以用類名.變數來獲取物件; - values()方法是獲取所有的列舉例項;
- valueOf(java.lang.String)是根據名稱獲取對應的例項;
二 列舉建立執行緒安全的單例模式
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
// dosomething...
}
}
這樣一個單例模式就建立好了,通過SingletonEnum.INSTANCE
來獲取物件就可以了。
2.1 序列化造成單例模式不安全
一個類如果如果實現了序列化介面,則可能破壞單例。每次反序列化一個序列化的一個例項物件都會建立一個新的例項。
列舉序列化是由JVM
保證的,每一個列舉型別和定義的列舉變數在JVM
中都是唯一的,在列舉型別的序列化和反序列化上,Java做了特殊的規定:在序列化時Java僅僅是將列舉物件的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum
的valueOf
方法來根據名字查詢列舉物件。同時,編譯器是不允許任何對這種序列化機制的定製的並禁用了writeObject
、readObject
、readObjectNoData
、writeReplace
和readResolve
等方法,從而保證了列舉例項的唯一性。
2.2 反射造成單例模式不安全
通過反射強行呼叫私有構造器來生成例項物件,造成單例模式不安全。
Class<?> aClass = Class.forName("xx.xx.xx");
Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
SingletonEnum singleton = (SingletonEnum) constructor.newInstance("Java旅途");
但是使用列舉建立的單例完全不用考慮這個問題,來看看newInstance
的原始碼!
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 如果是列舉型別,直接丟擲異常,不讓建立例項物件!
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
如果是enum
型別,則直接丟擲異常Cannot reflectively create enum objects
,無法通過反射建立例項物件!
三 通過列舉消除if/else
假如要寫一套加密介面,分別給小程式、app和web端來使用,但是這三種客戶端的加密方式不一樣。一般情況下我們會傳一個型別type
來判斷來源,然後呼叫對應的解密方法即可。程式碼如下:
if("WEIXIN".equals(type)){
// dosomething
}else if("APP".equals(type)){
// dosomething
}else if("WEB".equals(type)){
// dosomething
}
現在使用列舉來消除這些if/else。
寫一個加密用的介面,有加密和解密兩個方法。然後用不同的演算法去實現這個介面完成加解密。
public interface Util {
// 解密
String decrypt();
// 加密
String encrypt();
}
建立一個列舉類來實現這個介面
public enum UtilEnum implements Util {
WEIXIN {
@Override
public String decrypt() {
return "微信解密";
}
@Override
public String encrypt() {
return "微信加密";
}
},
APP {
@Override
public String decrypt() {
return "app解密";
}
@Override
public String encrypt() {
return "app加密";
}
},
WEB {
@Override
public String decrypt() {
return "web解密";
}
@Override
public String encrypt() {
return "web加密";
}
};
}
最後,獲取到type後,直接呼叫解密方法就行了。
String decryptMessage = UtilEnum.valueOf(type).decrypt();
以後,如果新增了一個其他加密方式,只需要修改上面的列舉類就完成了,業務程式碼都不需要改動。
這就是列舉類比較高階的兩個用法。
點關注、不迷路
如果覺得文章不錯,歡迎關注、點贊、收藏,你們的支援是我創作的動力,感謝大家。
如果文章寫的有問題,請不要吝嗇,歡迎留言指出,我會及時核查修改。
如果你還想更加深入的瞭解我,可以微信搜尋「Java旅途」進行關注。回覆「1024」即可獲得學習視訊及精美電子書。每天7:30準時推送技術文章,讓你的上班路不在孤獨,而且每月還有送書活動,助你提升硬實力!