<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java基礎不簡單,講一講枚舉

          共 11299字,需瀏覽 23分鐘

           ·

          2021-03-06 01:54

          文章已收錄Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary

          什么是枚舉

          枚舉是JDK1.5新增的一種數據類型,是一種特殊的類,常用于表示一組常量,比如一年四季,12個月份,星期一到星期天,服務返回的錯誤碼,結算支付的方式等等。枚舉是使用enum關鍵字來定義。

          枚舉的使用

          在使用枚舉之前我們先探討一個問題,為什么要使用枚舉。

          現在有個業(yè)務場景是結算支付,有支付寶和微信支付兩種方式,1表示支付寶,2表示微信支付,還需要根據編碼(1或2)獲取相應的英文名,如果不用枚舉,我們就要這樣寫。

          public?class?PayTypeUtil?{
          ????//支付寶
          ????private?static?final?int?ALI_PAY?=?1;
          ????//微信支付
          ????private?static?final?int?WECHAT_PAY?=?2;

          ????//根據編碼獲取支付方式的名稱
          ????public?String?getPayName(int?code)?{
          ????????if?(ALI_PAY?==?code)?{
          ????????????return?"Ali_Pay";
          ????????}
          ????????if?(WECHAT_PAY?==?code)?{
          ????????????return?"Wechat_Pay";
          ????????}
          ????????return?null;
          ????}
          }

          如果這時,產品經理說要增加一個銀聯支付,就要加多if的判斷,就會造成有多少種支付方式,就有多少個if,非常難看。

          如果使用枚舉,就變得很優(yōu)雅,先看代碼:

          public?enum?PayTypeEnum?{
          ????/**?支付寶*/
          ????ALI_PAY(1,?"ALI_PAY"),
          ????/**?微信支付*/
          ????WECHAT_PAY(2,?"WECHAT_PAY");

          ????private?int?code;

          ????private?String?describe;

          ????PayTypeEnum(int?code,?String?describe)?{
          ????????this.code?=?code;
          ????????this.describe?=?describe;
          ????}
          ????//根據編碼獲取支付方式
          ????public?PayTypeEnum?find(int?code)?{
          ????????for?(PayTypeEnum?payTypeEnum?:?values())?{
          ????????????if?(payTypeEnum.getCode()?==?code)?{
          ????????????????return?payTypeEnum;
          ????????????}
          ????????}
          ????????return?null;
          ????}
          ????//getter、setter方法
          }

          當我們需要擴展,只需要定義多一個實例即可,其他代碼都不用動,比如加多一個銀聯支付。

          /**?支付寶*/
          ALI_PAY(1,?"ALI_PAY"),
          /**?微信支付*/
          WECHAT_PAY(2,?"WECHAT_PAY"),
          //只需要加多一行代碼即可完成擴展
          /**?銀聯支付*/
          UNION_PAY(3,"UNION_PAY");

          一般在實際項目中,最多的寫法就是這樣,主要是簡單明了,易于擴展。

          第二種常見的用法是結合switch-case使用,比如我定義一個一年四季的枚舉。

          public?enum?Season?{
          ????//春
          ????SPRING,
          ????//夏
          ????SUMMER,?
          ????//秋
          ????AUTUMN,?
          ????//冬
          ????WINTER;
          }

          然后結合switch使用。

          public?static?void?main(String[]?args)?throws?Exception{
          ????doSomething(Season.SPRING);
          }

          private?static?void?doSomething(Season?season){
          ????switch?(season){
          ????????case?SPRING:
          ????????????System.out.println("不知細葉誰裁出,二月春風似剪刀");
          ????????????break;
          ????????case?SUMMER:
          ????????????System.out.println("接天蓮葉無窮碧,映日荷花別樣紅");
          ????????????break;
          ????????case?AUTUMN:
          ????????????System.out.println("停車坐愛楓林晚,霜葉紅于二月花");
          ????????????break;
          ????????case?WINTER:
          ????????????System.out.println("梅花香自苦寒來,寶劍鋒從磨礪出");
          ????????????break;
          ????????default:
          ????????????System.out.println("垂死病中驚坐起,笑問客從何處來");
          ????}
          }

          可能很多人覺得直接用int,String類型配合switch使用就夠了,為什么還要支持枚舉,這樣的設計是不是顯得很多余,其實非也。

          不妨反過來想,假如用1到4代表四季,接收的參數類型就是int,在沒有提示的情況下,我們僅僅只知道數int類型是很難猜到需要傳入數字的范圍,字符串也是一樣,如果不用枚舉你是很難一眼看出需要傳入什么參數,這才是最關鍵的。

          如果使用枚舉,那么問題就迎刃而解,當你調用doSomething()方法時,一看到枚舉就知道傳入的是哪幾個參數,因為已經在枚舉類里面定義好了。這對于項目交接,還有代碼的可讀性都是非常有利的

          這種限制不單止限制了調用方,也限制了傳入的參數只能是定義好的枚舉,不用擔心傳入的參數錯誤導致的程序錯誤。

          所以枚舉類使用得恰當,對于項目的可維護性是有很大提升的。

          枚舉本身的方法

          首先我們先以上面的支付類型枚舉PayTypeEnum為例子,看看有哪些自帶的方法。

          valueOf()方法

          這是一個靜態(tài)方法,傳入一個字符串(枚舉的名稱),獲取枚舉類。如果傳入的名稱不存在,則報錯。

          public?static?void?main(String[]?args)?throws?Exception{
          ????System.out.println(PayTypeEnum.valueOf("ALI_PAY"));
          ????System.out.println(PayTypeEnum.valueOf("HUAWEI_PAY"));
          }
          5e54b2e4371cb9845e145d3a49ba27aa.webp

          values()方法

          返回包含枚舉類中所有枚舉數據的一個數組。

          public?static?void?main(String[]?args)?throws?Exception?{
          ????PayTypeEnum[]?payTypeEnums?=?PayTypeEnum.values();
          ????for?(PayTypeEnum?payTypeEnum?:?payTypeEnums)?{
          ????????System.out.println("code:?"?+?payTypeEnum.getCode()?+?",describe:?"?+?payTypeEnum.getDescribe());
          ????}
          }
          f432b924d352c5dd10992653c349efe4.webp

          ordinal()方法

          默認情況下,枚舉類會給定義的枚舉提供一個默認的次序,ordinal()方法就可以返回枚舉的次序。

          public?static?void?main(String[]?args)?throws?Exception?{
          ????PayTypeEnum[]?payTypeEnums?=?PayTypeEnum.values();
          ????for?(PayTypeEnum?payTypeEnum?:?payTypeEnums)?{
          ????????System.out.println("ordinal:?"?+?payTypeEnum.ordinal()?+?",?Enum:?"?+?payTypeEnum);
          ????}
          }
          /**
          ordinal:?0,?Enum:?ALI_PAY
          ordinal:?1,?Enum:?WECHAT_PAY
          ordinal:?2,?Enum:?UNION_PAY
          */

          name()、toString()方法

          返回定義枚舉用的名稱。

          public?static?void?main(String[]?args)?throws?Exception?{
          ????for?(Season?season?:?Season.values())?{
          ????????System.out.println(season.name());
          ????}
          ????for?(Season?season?:?Season.values())?{
          ????????System.out.println(season.toString());
          ????}
          }

          輸出結果都是一樣的:

          SPRING
          SUMMER
          AUTUMN
          WINTER

          為什么?因為底層代碼是一樣,返回的是name。

          public?abstract?class?Enum<E?extends?Enum<E>>?implements?Comparable<E>,?Serializable?{
          ????
          ????public?final?String?name()?{
          ????????return?name;
          ????}
          ????
          ????public?String?toString()?{
          ????????return?name;
          ????}
          }
          ?

          區(qū)別在于toString()方法沒有被final修飾,可以重寫,name()方法不能重寫。

          compareTo()方法

          因為枚舉類實現了Comparable接口,所以必須重寫compareTo()方法,比較的是枚舉的次序,也就是ordinal,源碼如下:

          public?final?int?compareTo(E?o)?{
          ????Enum<?>?other?=?(Enum<?>)o;
          ????Enum<E>?self?=?this;
          ????if?(self.getClass()?!=?other.getClass()?&&?//?optimization
          ????????self.getDeclaringClass()?!=?other.getDeclaringClass())
          ????????throw?new?ClassCastException();
          ????return?self.ordinal?-?other.ordinal;
          }

          因為實現Comparable接口,所以可以用來排序,比如這樣:

          public?static?void?main(String[]?args)?throws?Exception?{
          ????//這里是亂序的枚舉數組
          ????Season[]?seasons?=?new?Season[]{Season.WINTER,?Season.AUTUMN,?Season.SPRING,?Season.SUMMER};
          ????//調用sort方法排序,按默認次序排序
          ????Arrays.sort(seasons);
          ????for?(Season?season?:?seasons)?{
          ????????System.out.println(season);
          ????}
          }

          輸出結果,按照默認次序排序:

          SPRING
          SUMMER
          AUTUMN
          WINTER
          原理

          以枚舉Season為例,分析一下枚舉的底層。表面上看,一個枚舉很簡單:

          public?enum?Season?{
          ????//春
          ????SPRING,
          ????//夏
          ????SUMMER,
          ????//秋
          ????AUTUMN,
          ????//冬
          ????WINTER;
          }

          實際上編譯器在編譯的時候做了很多動作,我們使用javap -v對Season.class文件反編譯,可以看到很多細節(jié)。

          首先我們看到枚舉是繼承了抽象類Enum的類。

          Season?extends?java.lang.Enum<Season>

          第二,通過一段靜態(tài)代碼塊初始化枚舉。

          ??static?{};
          ????descriptor:?()V
          ????flags:?ACC_STATIC
          ????Code:
          ??????stack=4,?locals=0,?args_size=0
          ?????????0:?new???????????#4??????????????????//?class?io/github/yehongzhi/user/redisLock/Season
          ?????????3:?dup
          ?????????4:?ldc???????????#7??????????????????//?String?SPRING
          ?????????6:?iconst_0
          ?????????7:?invokespecial?#8??????????????????//?Method?"<init>":(Ljava/lang/String;I)V
          ????????10:?putstatic?????#9??????????????????//?Field?SPRING:Lio/github/yehongzhi/user/redisLock/Season;
          ????????13:?new???????????#4??????????????????//?class?io/github/yehongzhi/user/redisLock/Season
          ????????16:?dup
          ????????17:?ldc???????????#10?????????????????//?String?SUMMER
          ????????19:?iconst_1
          ????????20:?invokespecial?#8??????????????????//?Method?"<init>":(Ljava/lang/String;I)V
          ????????23:?putstatic?????#11?????????????????//?Field?SUMMER:Lio/github/yehongzhi/user/redisLock/Season;
          ????????26:?new???????????#4??????????????????//?class?io/github/yehongzhi/user/redisLock/Season
          ????????29:?dup
          ????????30:?ldc???????????#12?????????????????//?String?AUTUMN
          ????????32:?iconst_2
          ????????33:?invokespecial?#8??????????????????//?Method?"<init>":(Ljava/lang/String;I)V
          ????????36:?putstatic?????#13?????????????????//?Field?AUTUMN:Lio/github/yehongzhi/user/redisLock/Season;
          ????????39:?new???????????#4??????????????????//?class?io/github/yehongzhi/user/redisLock/Season
          ????????42:?dup
          ????????43:?ldc???????????#14?????????????????//?String?WINTER
          ????????45:?iconst_3
          ????????46:?invokespecial?#8??????????????????//?Method?"<init>":(Ljava/lang/String;I)V
          ????????49:?putstatic?????#15?????????????????//?Field?WINTER:Lio/github/yehongzhi/user/redisLock/Season;
          ????????52:?iconst_4
          ????????53:?anewarray?????#4??????????????????//?class?io/github/yehongzhi/user/redisLock/Season
          ????????56:?dup
          ????????57:?iconst_0
          ????????58:?getstatic?????#9??????????????????//?Field?SPRING:Lio/github/yehongzhi/user/redisLock/Season;
          ????????61:?aastore
          ????????62:?dup
          ????????63:?iconst_1
          ????????64:?getstatic?????#11?????????????????//?Field?SUMMER:Lio/github/yehongzhi/user/redisLock/Season;
          ????????67:?aastore
          ????????68:?dup
          ????????69:?iconst_2
          ????????70:?getstatic?????#13?????????????????//?Field?AUTUMN:Lio/github/yehongzhi/user/redisLock/Season;
          ????????73:?aastore
          ????????74:?dup
          ????????75:?iconst_3
          ????????76:?getstatic?????#15?????????????????//?Field?WINTER:Lio/github/yehongzhi/user/redisLock/Season;
          ????????79:?aastore
          ????????80:?putstatic?????#1??????????????????//?Field?$VALUES:[Lio/github/yehongzhi/user/redisLock/Season;
          ????????83:?return

          這段靜態(tài)代碼塊的作用就是生成四個靜態(tài)常量字段的值,還生成了$VALUES字段,用于保存枚舉類定義的枚舉常量。相當于執(zhí)行了以下代碼:

          Season?SPRING?=?new?Season1();
          Season?SUMMER?=?new?Season2();
          Season?AUTUMN?=?new?Season3();
          Season?WINTER?=?new?Season4();
          Season[]?$VALUES?=?new?Season[4];
          $VALUES[0]?=?SPRING;
          $VALUES[1]?=?SUMMER;
          $VALUES[2]?=?AUTUMN;
          $VALUES[3]?=?WINTER;

          第三個,關于values()方法,這是一個靜態(tài)方法,作用是返回該枚舉類的數組,底層實現原理,其實是這樣的。

          public?static?io.github.yehongzhi.user.redisLock.Season[]?values();
          ????Code:
          ???????0:?getstatic?????#1??????????????????//?Field?$VALUES:[Lio/github/yehongzhi/user/redisLock/Season;
          ???????3:?invokevirtual?#2??????????????????//?Method?"[Lio/github/yehongzhi/user/redisLock/Season;".clone:()Ljava/lang/Object;
          ???????6:?checkcast?????#3??????????????????//?class?"[Lio/github/yehongzhi/user/redisLock/Season;"
          ???????9:?areturn

          其實是將靜態(tài)代碼塊初始化的$VALUES數組克隆一份,然后強轉成Season[]返回。相當于這樣:

          public?static?Season[]?values(){
          ?return?(Season[])$VALUES.clone();
          }

          所以表面上,只是加了一個enum關鍵字定義枚舉,但是底層一旦確認是枚舉類,則會由編譯器對枚舉類進行特殊處理,通過靜態(tài)代碼塊初始化枚舉,只要是枚舉就一定會提供values()方法。

          通過反編譯我們也知道所有的枚舉父類都是抽象類Enum,所以Enum有的成員變量,實現的接口,子類也會有。

          所以只要是枚舉都會有name,ordinal這兩個字段,并且我們看Enum的構造器。

          /**
          *?Sole?constructor.??Programmers?cannot?invoke?this?constructor.
          *?It?is?for?use?by?code?emitted?by?the?compiler?in?response?to
          *?enum?type?declarations.
          */

          protected?Enum(String?name,?int?ordinal)?{
          ????this.name?=?name;
          ????this.ordinal?=?ordinal;
          }

          翻譯一下上面那段英文,意思大概是:唯一的構造器,程序員沒法調用此構造器,它是供編譯器響應枚舉類型聲明而使用的。得出結論,枚舉實例的創(chuàng)建也是由編譯器完成的。

          枚舉實現單例

          很多人都說,枚舉類是最好的實現單例的一種方式,因為枚舉類的單例是線程安全,并且是唯一一種不會被破壞的單例模式實現。也就是不能通過反射的方式創(chuàng)建實例,保證了整個應用中只有一個實例,非常硬核的單例。

          public?class?SingletonObj?{
          ????//內部類使用枚舉
          ????private?enum?SingletonEnum?{
          ????????INSTANCE;

          ????????private?SingletonObj?singletonObj;
          ??????//在枚舉類的構造器里初始化singletonObj
          ????????SingletonEnum()?{
          ????????????singletonObj?=?new?SingletonObj();
          ????????}

          ????????private?SingletonObj?getSingletonObj()?{
          ????????????return?singletonObj;
          ????????}
          ????}

          ????//對外部提供的獲取單例的方法
          ????public?static?SingletonObj?getInstance()?{
          ????????//獲取單例對象,返回
          ????????return?SingletonEnum.INSTANCE.getSingletonObj();
          ????}

          ????//測試
          ????public?static?void?main(String[]?args)?{
          ????????SingletonObj?a?=?SingletonObj.getInstance();
          ????????SingletonObj?b?=?SingletonObj.getInstance();
          ????????System.out.println(a?==?b);//true
          ????}
          }

          假如有人想通過反射創(chuàng)建枚舉類呢,我們以Season枚舉為例。

          public?static?void?main(String[]?args)?throws?Exception?{
          ????Constructor<Season>?constructor?=?Season.class.getDeclaredConstructor(String.class,?int.class);
          ????constructor.setAccessible(true);
          ????//通過反射調用構造器,創(chuàng)建枚舉
          ????Season?season?=?constructor.newInstance("NEW_SPRING",?4);
          ????System.out.println(season);
          }

          然后就會報錯,因為不允許對枚舉的構造器使用反射調用。

          06910d9b2981fecef035b1cba2526edd.webp

          查看源碼,就可以看到,有個專門針對枚舉的if判斷。

          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)
          ????????//拋出異常,不能通過反射創(chuàng)建枚舉
          ????????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;
          }
          總結

          枚舉看起來好像是很小一部分的知識,其實深入挖掘的話,我們會發(fā)現還是有很多地方值得學習的。第一點使用枚舉定義常量更容易擴展,而且代碼可讀性更強,維護性更好。接著第二點是需要了解枚舉自帶的方法。第三點通過反編譯,探索編譯器在編譯階段為枚舉做了什么事情。最后再講一下枚舉實現單例模式的例子。

          這篇文章講到這里了,感謝大家的閱讀,希望看完這篇文章能有所收獲!

          覺得有用就點個贊吧,你的點贊是我創(chuàng)作的最大動力~

          我是一個努力讓大家記住的程序員。我們下期再見!!!

          能力有限,如果有什么錯誤或者不當之處,請大家批評指正,一起學習交流!

          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美三级少妇 | 天天干天天射天天舔麻豆 | 操少妇| 成人青娱乐 | 欧美最大操逼网站在线 |