Coder,我懷疑你并不會(huì)枚舉
枚舉是JDK1.5引入的新特性。被enum關(guān)鍵字修飾的類就是一個(gè)枚舉類。
關(guān)于枚舉,阿里巴巴開發(fā)手冊(cè)有這樣兩條建議:
枚舉類名帶上 Enum 后綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。
如果變量值僅在一個(gè)固定范圍內(nèi)變化用 enum 類型來(lái)定義。
一 枚舉類有哪些特點(diǎn)
創(chuàng)建一個(gè)ColorEnum的枚舉類,通過(guò)編譯,再反編譯看看它發(fā)生了哪些變化。
- public enum ColorEnum {
- RED,GREEN,BULE;
- }
使用命令javac ColorEnum.java進(jìn)行編譯生成class文件,然后再用命令javap -p ColorEnum.class進(jìn)行反編譯。
去掉包名,反編譯后的內(nèi)容如下:
- 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修飾,因此枚舉類不能被繼承;
- 枚舉類默認(rèn)繼承了Enum類,java不支持多繼承,因此枚舉類不能繼承其他類;
- 枚舉類的構(gòu)造器是private修飾的,因此其他類不能通過(guò)構(gòu)造器來(lái)獲取對(duì)象;
- 枚舉類的成員變量是static修飾的,可以用類名.變量來(lái)獲取對(duì)象;
- values()方法是獲取所有的枚舉實(shí)例;
- valueOf(java.lang.String)是根據(jù)名稱獲取對(duì)應(yīng)的實(shí)例;
二 枚舉創(chuàng)建線程安全的單例模式
- public enum SingletonEnum {
- INSTANCE;
- public void doSomething(){
- // dosomething...
- }
- }
這樣一個(gè)單例模式就創(chuàng)建好了,通過(guò)SingletonEnum.INSTANCE來(lái)獲取對(duì)象就可以了。
2.1 序列化造成單例模式不安全
一個(gè)類如果如果實(shí)現(xiàn)了序列化接口,則可能破壞單例。每次反序列化一個(gè)序列化的一個(gè)實(shí)例對(duì)象都會(huì)創(chuàng)建一個(gè)新的實(shí)例。
枚舉序列化是由JVM保證的,每一個(gè)枚舉類型和定義的枚舉變量在JVM中都是唯一的,在枚舉類型的序列化和反序列化上,Java做了特殊的規(guī)定:在序列化時(shí)Java僅僅是將枚舉對(duì)象的name屬性輸出到結(jié)果中,反序列化的時(shí)候則是通過(guò)java.lang.Enum的valueOf方法來(lái)根據(jù)名字查找枚舉對(duì)象。同時(shí),編譯器是不允許任何對(duì)這種序列化機(jī)制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,從而保證了枚舉實(shí)例的唯一性。
2.2 反射造成單例模式不安全
通過(guò)反射強(qiáng)行調(diào)用私有構(gòu)造器來(lái)生成實(shí)例對(duì)象,造成單例模式不安全。
- Class<?> aClass = Class.forName("xx.xx.xx");
- Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
- SingletonEnum singleton = (SingletonEnum) constructor.newInstance("Java旅途");
但是使用枚舉創(chuàng)建的單例完全不用考慮這個(gè)問題,來(lái)看看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);
- }
- }
- // 如果是枚舉類型,直接拋出異常,不讓創(chuàng)建實(shí)例對(duì)象!
- 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,無(wú)法通過(guò)反射創(chuàng)建實(shí)例對(duì)象!
三 通過(guò)枚舉消除if/else
假如要寫一套加密接口,分別給小程序、app和web端來(lái)使用,但是這三種客戶端的加密方式不一樣。一般情況下我們會(huì)傳一個(gè)類型type來(lái)判斷來(lái)源,然后調(diào)用對(duì)應(yīng)的解密方法即可。代碼如下:
- if("WEIXIN".equals(type)){
- // dosomething
- }else if("APP".equals(type)){
- // dosomething
- }else if("WEB".equals(type)){
- // dosomething
- }
現(xiàn)在使用枚舉來(lái)消除這些if/else。
寫一個(gè)加密用的接口,有加密和解密兩個(gè)方法。然后用不同的算法去實(shí)現(xiàn)這個(gè)接口完成加解密。
- public interface Util {
- // 解密
- String decrypt();
- // 加密
- String encrypt();
- }
創(chuàng)建一個(gè)枚舉類來(lái)實(shí)現(xiàn)這個(gè)接口
- 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后,直接調(diào)用解密方法就行了。
- String decryptMessage = UtilEnum.valueOf(type).decrypt();
以后,如果新增了一個(gè)其他加密方式,只需要修改上面的枚舉類就完成了,業(yè)務(wù)代碼都不需要改動(dòng)。
這就是枚舉類比較高級(jí)的兩個(gè)用法。
本文轉(zhuǎn)載自微信公眾號(hào)「Java旅途」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java旅途公眾號(hào)。