全面解讀Java中的枚舉類型enum的使用
關(guān)于枚舉
大多數(shù)地方寫(xiě)的枚舉都是給一個(gè)枚舉然后例子就開(kāi)始switch,可是我想說(shuō),我代碼里頭來(lái)源的數(shù)據(jù)不太可能就是枚舉,通常是字符串或數(shù)字,比如一個(gè)SQL我解析后首先判定SQL類型,通過(guò)截取SQL的token,截取出來(lái)可能是SELECT、DELETE、UPDATE、INSERT、ALTER等等,但是都是字符串,此時(shí)我想用枚舉就不行了,我要將字符串轉(zhuǎn)換成枚舉怎么轉(zhuǎn)呢,類似的情況還有從數(shù)據(jù)庫(kù)取出數(shù)據(jù)根據(jù)一些類型做判定,從頁(yè)面?zhèn)魅霐?shù)據(jù),根據(jù)不同的類型做不同的操作,但是都是字符串,不是枚舉,悲劇的是我很少看到有人寫(xiě)到這個(gè)東西;所以我把它寫(xiě)下來(lái),希望有人能用到。
首先為什么要用枚舉?我們?cè)谑裁磿r(shí)候用枚舉比較好,用枚舉有啥優(yōu)勢(shì)?
我認(rèn)為哈,當(dāng)你在一些一個(gè)范疇類,并可列舉,不變化的類型,用以指導(dǎo)程序向不同的地方路由,用枚舉是較好的選擇;
聽(tīng)起來(lái)有點(diǎn)繞,不過(guò)有個(gè)例子也許可以明白,例如:
我們可以列舉下日常工作日所做的事情:
上班、開(kāi)會(huì)、吃飯、睡覺(jué)等
我們可以列舉醫(yī)院五官科需要檢查人的部位:
眼睛、鼻子、耳朵、嘴巴等
這些都是可以被列舉的,且每種事情我們要用不同的方式去做;
當(dāng)然你可以說(shuō):
1、可以用動(dòng)態(tài)方法分派,通過(guò)配置文件或annotation;
2、可以使用常量來(lái)達(dá)到類似的效果;
3、直接通過(guò)字符串的equals來(lái)表達(dá),用if else來(lái)表達(dá)
如果用配置加方法分派來(lái)做,是靈活,便于修改;但是如果在很多不經(jīng)常修改的參數(shù)上,我們用這中方式往往增加配置的負(fù)擔(dān),并且當(dāng)你需要看系統(tǒng)邏輯的時(shí)候,需要需要一遍看配置一遍看代碼;不過(guò),如果參數(shù)是可動(dòng)態(tài)變換的信息,用配置是正確的選擇;
而常量的使用,通常在switch case的時(shí)候都是數(shù)字,字符串在java中是不能做switch case的,使用常量的目的比case 1、case 2 …這種增加了可讀性;但是字符串?dāng)?shù)據(jù)也麻煩,除非再映射一次,那沒(méi)那個(gè)必要,其實(shí)枚舉也差不多是幫你映射了一次,只是它將代碼封裝了而已吧了,既然他弄好了,而且語(yǔ)法上支持,干嘛不用呢!其次,常量雖然增加了可讀性,不過(guò)他沒(méi)有范疇和管理類型的概念,即一個(gè)枚舉的定義會(huì)定義個(gè)范疇,可以很好的將這個(gè)范圍所需要的東西列舉出來(lái),而常量通常是些自己定義的一些池,放在一些公共類中或隨機(jī)定義,都是比較零散的,并且枚舉在switch的時(shí)候就明確定義好了就在鎖列舉的范圍內(nèi)case,既可以控制好系統(tǒng),增加可讀性,并且可以隨時(shí)查看這個(gè)范疇的枚舉信息到底有那些,達(dá)到類似看配置文件的作用;不過(guò)還是回到那句話,如果參數(shù)是可變的,那么就不適合做枚舉,枚舉是一定是可列舉的,或者說(shuō)當(dāng)前系統(tǒng)考慮范圍是可以被枚舉的,例如上面的醫(yī)院五官科,可能還有很多沒(méi)有列舉到,但是當(dāng)前醫(yī)院只處理幾個(gè)部位,不處理其他的,就是這個(gè)道理;什么是可變的呢,例如URL參數(shù)來(lái)分派到對(duì)應(yīng)方法,不可能大家加一段邏輯就去加一個(gè)枚舉,加一個(gè)case,此時(shí)用【配置+動(dòng)態(tài)方法分派】更好,當(dāng)然配置可以用文件或annotation而已。
還有最土的就是,通過(guò)字符串equals,用if else來(lái)實(shí)現(xiàn),呵呵,這個(gè)并沒(méi)有什么不好,只是這個(gè)寫(xiě)比較零散,其次,字符串匹配的equals每次匹配都需要對(duì)比每個(gè)字符,如果你的代碼中大量循環(huán),性能并不是很好,其余的看看上面的描述就更加清楚了;
其次,枚舉提供一種類型管理的組件,讓面向?qū)ο蟮捏w系更加完善,使得一些類型的管理既可配置化,并可以管理,在使用枚舉的地方都可以沿著枚舉的定義找到那些有處理過(guò),那些沒(méi)處理過(guò),而上述幾種很難做到;例如,數(shù)據(jù)庫(kù)的操作類型定義了10種,那么再判定的過(guò)程中就可以講枚舉像配置文件一樣看待,而又非常簡(jiǎn)單的來(lái)管理。
最后,枚舉絕對(duì)是單例的,對(duì)比的性能和數(shù)字性能相當(dāng),既可以得到可讀性,也可以得到性能。
enum類型的基本使用
有了這樣的理論基礎(chǔ),我們下面就來(lái)看Java中的enum枚舉類型:
1、可以在enum中添加變量和方法
先來(lái)看一段代碼示例:
public enum State { Normal("正常態(tài)", 1), Update("已更新", 2), Deleted("已刪除", 3), Fired("已屏蔽", 4); // 成員變量 private String name; private int index; // 構(gòu)造方法,注意:構(gòu)造方法不能為public,因?yàn)閑num并不可以被實(shí)例化 private State(String name, int index) { this.name = name; this.index = index; } // 普通方法 public static String getName(int index) { for (State c : State .values()) { if (c.getIndex() == index) { return c.name; } } return null; } // get set 方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } }
從上面的代碼中我們可以看到,定義完枚舉值,然后在其后面加上分號(hào),接著就可以定義其他的變量、方法了。另外需要特別說(shuō)明的是,enum中的構(gòu)造方法不可以用public標(biāo)識(shí),這樣做是為了防止用戶實(shí)例化enum。
2、可以用來(lái)定義常量
先來(lái)回顧一下Java中如何定義常量吧,看下面一段代碼:
public static final int normalState = 1; private static final int updateState = 2;
下面我們還可以用enum枚舉來(lái)代替上面的常量定義,代碼如下:
public enum State { Normal, Update, Deleted, Fired }
在Java中用enum來(lái)定義常量在語(yǔ)法上沒(méi)有什么優(yōu)勢(shì),但是enum枚舉類型可以提供更多的操作功能。
3、在enum中實(shí)現(xiàn)接口
先來(lái)看下面一段代碼:
public interface ICanReadState { void read(); String getState(); } public enum State implements ICanReadState { Normal("正常態(tài)", 1), Update("已更新", 2), Deleted("已刪除", 3), Fired("已屏蔽", 4); private String name; private int index; private State(String name, int index) { this.name = name; this.index = index; } // 接口方法1 @Override public String getState() { return this.name; } // 接口方法2 @Override public void read() { System.out.println(this.index + ":" + this.name); } }
和一般的類中使用接口一樣,enum枚舉中同樣可以繼承接口,并實(shí)現(xiàn)接口中的所有方法,這樣做的好處在于可以更方便地對(duì)枚舉中的值進(jìn)行排序、比較等操作,封裝性更好。
實(shí)例
我們先定義個(gè)簡(jiǎn)單枚舉(這里只是個(gè)例子,就簡(jiǎn)單定義3個(gè)變量了):
public enum SqlTypeEnum { INSERT , UPDATE , DELETE , SELECT }
此時(shí)解析SQL后,獲取出來(lái)一個(gè)token,我們要獲取這個(gè)token的枚舉怎么獲取呢?
這樣獲取:
String token = "select"; SqlTypeEnum sqlTypeEnum = SqlTypeEnum.valueOf(token.toUpperCase());
如果沒(méi)獲取到,java會(huì)拋出一個(gè)異常哦:IllegalArgumentException No enum const class SqlTypeEnum.XXX
我做大寫(xiě)處理的原因是因?yàn)槊杜e也是大寫(xiě)的(當(dāng)然如果你的枚舉是小寫(xiě)的,那你就小寫(xiě),不過(guò)混寫(xiě)比較麻煩哈),其實(shí)valueOf就是調(diào)用了枚舉的底層映射:
調(diào)用的時(shí)候會(huì)調(diào)用這個(gè)方法:
所以內(nèi)部也是一個(gè)HashMap,呵呵!
拿到這個(gè)信息后,就可以做想要的操作了:
switch(sqlTypeEnum) { case INSERT:處理insert邏輯;break; case DELETE:處理delete邏輯;break; .... }
OK,有些時(shí)候可能我們不想直接用INSERT、UPDATE這樣的字符串在交互中使用,因?yàn)楹芏鄷r(shí)候命名規(guī)范的要求;
例如定義一些用戶操作類型:
1、保存用戶信息
2、通過(guò)ID獲取用戶基本信息
3、獲取用戶列表
4、通過(guò)ID刪除用戶信息
等等
我們可能定義枚舉會(huì)定義為:
public enum UserOptionEnum { SAVE_USER, GET_USER_BY_ID, GET_USER_LIST, DELETE_USER_BY_ID }
但是系統(tǒng)的方法和一些關(guān)鍵字的配置,通常會(huì)寫(xiě)成:
saveUser、getUserById、getUserById、deleteUserById
當(dāng)然各自有各自的規(guī)則,不過(guò)中間這層映射,你不想做,就一方面妥協(xié),要么枚舉名稱全部換掉,貌似挺奇怪的,要么方法名稱全部換掉,更加奇怪,要么自己做映射,可以,稍微麻煩點(diǎn),其實(shí)也不麻煩?
我們首先寫(xiě)個(gè)將枚舉下劃線風(fēng)格的數(shù)據(jù)轉(zhuǎn)換為駝峰的方法,放在一個(gè)StringUtils里面:
public static String convertDbStyleToJavaStyle(String dbStyleString , boolean firstUpper) { dbStyleString = dbStyleString.toLowerCase(); String []tokens = dbStyleString.split("_"); StringBuilder stringBuilder = new StringBuilder(128); int length = 0; for(String token : tokens) { if(StringUtils.isNotBlank(token)) { if(length == 0 && !firstUpper) { stringBuilder.append(token); }else { char c = token.charAt(0); if(c >= 'a' || c <= 'z') c = (char)(c - 32); stringBuilder.append(c); stringBuilder.append(token.substring(1)); } } ++length; } return stringBuilder.toString(); }
重載一個(gè)方法:
public static String convertDbStyleToJavaLocalStyle(String dbStyleString) { return convertDbStyleToJavaStyle(dbStyleString , false); }
然后定義枚舉:
public enum UserOptionEnum { SAVE_USER, GET_USER_BY_ID, GET_USER_LIST, DELETE_USER_BY_ID; private final static Map<String , UserOptionEnum> ENUM_MAP = new HashMap<String, UserOptionEnum>(64); static { for(UserOptionEnum v : values()) { ENUM_MAP.put(v.toString() , v); } } public staticUserOptionEnum fromString(String v) { UserOptionEnum userOptionEnum = ENUM_MAP.get(v); return userOptionEnum == null ? DEFAULT :userOptionEnum; } public String toString() { String stringValue = super.toString(); return StringUtil.convertDbStyleToJavaLocalStyle(stringValue); } }
OK,這樣傳遞一個(gè)event參數(shù)讓如果是:saveUser,此時(shí)就用:
String event = "saveUser";//假如這里得到參數(shù) UserOptionEnum enum = UserOptionEnum.fromString(event);
其實(shí)就是自己做了一個(gè)hashMap,我這加了一個(gè)fromString,因?yàn)槊杜e有一些限制,有些方法不讓你覆蓋,比如valueOf方法就是這樣。
其實(shí)沒(méi)啥好講的了,非要說(shuō),再說(shuō)說(shuō)枚舉加一些自定義變量吧,其實(shí)枚舉除了是單例的外,其余的和普通類也相似,它也可以有構(gòu)造方法,只是默認(rèn)情況下不是而已,也可以提供自定義的變量,然后獲取set、get方法,但是如果有set的話,線程不是安全的哦,要注意這點(diǎn);所以一般是構(gòu)造方法就寫(xiě)好了:
public enum SqlTypeEnum { INSERT("insert into"), DELETE("delete from") ......省略; private String name;//定義自定義的變量 private SqlTypeEnum(String name) { this.name = name; } public String getName() { return name; } public String toString() { return name + " 我靠";//重寫(xiě)toString方法 } //一般不推薦 public void setName(String name) { this.name = name; } }
調(diào)用下:
SqlTypeEnum sqlTypeEnum = SqlTypeEnum.valueOf("INSERT"); System.out.println(sqlTypeEnum); System.out.println(sqlTypeEnum.getName());
不推薦也調(diào)用下:
sqlTypeEnum.setName("我靠");
在另一個(gè)線程:
SqlTypeEnum sqlTypeEnum = SqlTypeEnum.valueOf("INSERT"); System.out.println(sqlTypeEnum); System.out.println(sqlTypeEnum.getName());
發(fā)現(xiàn)結(jié)果被改了,呵呵!
相關(guān)文章
解決在SpringBoot中使用@Value取不到值的問(wèn)題
這篇文章主要給大家分享解決在SpringBoot中使用@Value取不到值的問(wèn)題,文中有詳細(xì)的解決代碼供大家參考,具有一定的參考價(jià)值,需要的朋友可以參考下2023-09-09JAVA面向?qū)ο笾^承?super入門(mén)解析
在JAVA類中使用super來(lái)引用父類的成分,用this來(lái)引用當(dāng)前對(duì)象,如果一個(gè)類從另外一個(gè)類繼承,我們new這個(gè)子類的實(shí)例對(duì)象的時(shí)候,這個(gè)子類對(duì)象里面會(huì)有一個(gè)父類對(duì)象。怎么引用里面的父類對(duì)象呢?用super來(lái)引用,this指當(dāng)前對(duì)象的引用,super是當(dāng)前對(duì)象里面的父對(duì)象的引用2022-01-01springboot的logging.group日志分組方法源碼流程解析
這篇文章主要為大家介紹了springboot的logging.group日志分組方法源碼流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Java如何獲取對(duì)象屬性及對(duì)應(yīng)值
這篇文章主要介紹了Java如何獲取對(duì)象屬性及對(duì)應(yīng)值,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11基于Spring Data Jest的Elasticsearch數(shù)據(jù)統(tǒng)計(jì)示例
本篇文章主要介紹了基于Spring Data Jest的Elasticsearch數(shù)據(jù)統(tǒng)計(jì)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02詳談異步log4j2中的location信息打印問(wèn)題
這篇文章主要介紹了詳談異步log4j2中的location信息打印問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12IDEA無(wú)法打開(kāi)Marketplace的三種解決方案(推薦)
這篇文章主要介紹了IDEA無(wú)法打開(kāi)Marketplace的三種解決方案(推薦),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11詳談java中int和Integer的區(qū)別及自動(dòng)裝箱和自動(dòng)拆箱
這篇文章主要介紹了詳談java中int和Integer的區(qū)別及自動(dòng)裝箱和自動(dòng)拆箱,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08