你连简单的枚举类都不知道,还敢说自己会Java???滚出我的公司
枚舉類型是Java 5中新增的特性,它是一種特殊的數據類型,之所以特殊是因為它既是一種類(class)類型卻又比類類型多了些特殊的約束,但是這些約束的存在也造就了枚舉類型的簡潔性、安全性以及便捷性。當需要定義一組常量時,強烈建議使用枚舉類。
使用枚舉類的條件:類的對象是有限個,確定的。例如星期類,它的對象只有星期一…星期日七個,而且是確定的,此時就可以把星期類定義為一個枚舉類;又例如性別類,它的對象只有男和女兩個,而且是確定的,此時同樣可以把性別類定義為一個枚舉類;還有諸如季節等這種類的對象是有限個,確定的都可以定義為一個枚舉類。
1、枚舉類的實現
在JDK1.5之前,還沒有枚舉類型,如果想要使用枚舉類需要我們去自定義。在自定義枚舉類時需要注意以下幾點:
(1)枚舉類對象的屬性不應允許被改動,所以應該使用 private final 進行修飾;
(2)枚舉類使用 private final 修飾的屬性應該在構造器中為其賦值;
(3)枚舉類的構造器要私有化,保證不能在類的外部創建其對象,否則就不能確定對象的個數;
(4)在枚舉類內部創建的枚舉類的實例(枚舉)對象,要聲明為:public static final。
下面就拿季節舉例,來自定義一個枚舉類。
public class Season {//1.聲明Season對象的屬性,又因為枚舉類對象的屬性不應允許被改動, 所以應該使用 private final修飾//枚舉類的使用 private final 修飾的屬性應該在構造器中為其賦值private final String seasonName;private final String seasonDesc;//2.私有化構造器,保證不能在類的外部創建其對象,否則就不能確定對象的個數private Season(String seasonName,String seasonDesc){this.seasonName=seasonName;this.seasonDesc=seasonDesc;}//3.提供當前枚舉類的多個枚舉對象,又因為枚舉類是不可變的常量類,所以需要聲明為:public static finalpublic static final Season SPRING=new Season("春天","鳥語花香");public static final Season SUMMER=new Season("夏天","夏日炎炎");public static final Season AUTUMN=new Season("秋天","秋高氣爽");public static final Season WINNER=new Season("冬天","寒風瑟瑟");//其他需求1:獲取枚舉類對象的屬性//只需要提供屬性的get方法即可,但是不能提供set方法,因為枚舉類是不可變的常量類,不能被修改public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}//其他需求2:打印對象,提供toString方法即可@Overridepublic String toString() {return "Season{" +"seasonName='" + seasonName + '\'' +", seasonDesc='" + seasonDesc + '\'' +'}';} } public class SeasonTest {public static void main(String[] args) {Season spring = Season.SPRING;System.out.println(spring); //Season{seasonName='春天', seasonDesc='鳥語花香'}} }在JDK 1.5 中新增了enum關鍵字用于定義枚舉類,但是在使用時需要注意以下幾點:
(1)使用 enum 定義的枚舉類默認繼承了 java.lang.Enum類,因此不能再繼承其他類;
(2)使用 enum 定義的枚舉類默認使用final進行修飾,不可以被繼承;(也從側面說明了它是一個常量類)
(3)枚舉類的構造器只能使用 private 權限修飾符;
(4)枚舉類的所有實例必須在枚舉類中顯式列出,多個對象之間使用",“隔開,末尾使用”;"結束。
列出的實例系統會自動添加 public static final 進行修飾;
(5)必須在枚舉類的第一行聲明枚舉類對象;
(6)若枚舉類只有一個枚舉對象, 則可以作為一種單例模式的實現方式。
下面還是使用季節舉例,來自定義一個枚舉類。
//使用enum關鍵字定義枚舉類 public enum Season2 {//1.提供當前枚舉類的對象,多個對象之間使用","隔開,末尾使用";"結束//系統默認使用public static final修飾SPRING("春天","鳥語花香"),SUMMER("夏天","夏日炎炎"),AUTUMN("秋天","秋高氣爽"),WINNER("冬天","寒風瑟瑟");//2.聲明Season對象的屬性,又因為枚舉類對象的屬性不應允許被改動, 所以應該使用 private final修飾private final String seasonName;private final String seasonDesc;//3.枚舉類的構造器只能使用 private 權限修飾符// 私有化構造器是為了保證不能在類的外部創建其對象,否則就不能確定對象的個數private Season2(String seasonName, String seasonDesc){this.seasonName=seasonName;this.seasonDesc=seasonDesc;}//其他需求:獲取枚舉類對象的屬性//只需要提供屬性的get方法即可,但是不能提供set方法,而且也不允許提供set方法,因為枚舉類是不可變的常量類,不能被修改public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;} } public class SeasonTest {public static void main(String[] args) {Season2 spring = Season2.SPRING;System.out.println(spring);//SPRING} }2、Enum類中的常用方法
values()方法:返回枚舉類型的對象數組,該方法可以很方便地遍歷所有的枚舉值;
//使用方法如下:
Season2[] seasons = Season2.values(); for (int i = 0; i < seasons.length; i++) {System.out.println(seasons[i]); } valueOf(String str):可以把一個字符串轉為對應的枚舉類對象。要求字符串必須是枚舉類對象的“名字”。 如不是,會報運行時異常:IllegalArgumentException; //使用方法如下: Season2 spring = Season2.valueOf("SPRING"); System.out.println(spring);//SPRING toString():返回當前枚舉類對象的名稱 //使用方法如下: Season2 spring = Season2.SPRING; System.out.println(spring.toString());//SPRING3、使用enum關鍵字定義枚舉類實現接口
枚舉類和普通類一樣,可以實現一個或多個接口。枚舉類實現接口分為兩種情況:
情況一:若枚舉類的所有枚舉對象在調用實現的接口方法時,呈現相同的行為方式,則只要統一實現該方法即可;此時與普通類實現接口一樣,沒有任何區別。
public interface Show {void show(); } //使用enum關鍵字定義枚舉類 public enum Season2 implements Show{//1.提供當前枚舉類的對象,多個對象之間使用","隔開,末尾使用";"結束//系統默認使用public static final修飾SPRING("春天","鳥語花香"),SUMMER("夏天","夏日炎炎"),AUTUMN("秋天","秋高氣爽"),WINNER("冬天","寒風瑟瑟");//2.聲明Season對象的屬性,又因為枚舉類對象的屬性不應允許被改動, 所以應該使用 private final修飾private final String seasonName;private final String seasonDesc;//3.枚舉類的構造器只能使用 private 權限修飾符// 私有化構造器是為了保證不能在類的外部創建其對象,否則就不能確定對象的個數private Season2(String seasonName, String seasonDesc){this.seasonName=seasonName;this.seasonDesc=seasonDesc;}//其他需求:獲取枚舉類對象的屬性//只需要提供屬性的get方法即可,但是不能提供set方法,而且也不允許提供set方法,因為枚舉類是不可變的常量類,不能被修改public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}//重寫show()方法,與普通類實現接口一樣,沒有任何區別@Overridepublic void show() {System.out.println("一年四季:春夏秋冬");} } public class SeasonTest {public static void main(String[] args) {Season2 spring = Season2.SPRING;spring.show();Season2 summer = Season2.SUMMER;summer.show();Season2 autumn = Season2.AUTUMN;autumn.show();Season2 winner = Season2.WINNER;winner.show();} }運行結果:
 
情況二:若枚舉類的每個枚舉對象在調用實現的接口方法時,需要呈現出不同的行為方式,則可以讓每個枚舉對象分別來實現該方法
public interface Show {void show(); } //使用enum關鍵字定義枚舉類 public enum Season2 implements Show{//1.提供當前枚舉類的對象,多個對象之間使用","隔開,末尾使用";"結束//系統默認使用public static final修飾SPRING("春天","鳥語花香"){//每個枚舉對象分別來實現該方法@Overridepublic void show() {System.out.println("春天是一個鳥語花香的季節!");}},SUMMER("夏天","夏日炎炎"){@Overridepublic void show() {System.out.println("夏天是一個夏日炎炎的季節!");}},AUTUMN("秋天","秋高氣爽"){@Overridepublic void show() {System.out.println("秋天是一個秋高氣爽的季節!");}},WINNER("冬天","寒風瑟瑟"){@Overridepublic void show() {System.out.println("冬天是一個寒風瑟瑟的季節!");}};//2.聲明Season對象的屬性,又因為枚舉類對象的屬性不應允許被改動, 所以應該使用 private final修飾private final String seasonName;private final String seasonDesc;//3.枚舉類的構造器只能使用 private 權限修飾符// 私有化構造器是為了保證不能在類的外部創建其對象,否則就不能確定對象的個數private Season2(String seasonName, String seasonDesc){this.seasonName=seasonName;this.seasonDesc=seasonDesc;}//其他需求:獲取枚舉類對象的屬性//只需要提供屬性的get方法即可,但是不能提供set方法,而且也不允許提供set方法,因為枚舉類是不可變的常量類,不能被修改public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;} } public class SeasonTest {public static void main(String[] args) {Season2 spring = Season2.SPRING;spring.show();Season2 summer = Season2.SUMMER;summer.show();Season2 autumn = Season2.AUTUMN;autumn.show();Season2 winner = Season2.WINNER;winner.show();} }運行結果:
4、枚舉類對switch的語句的影響
Java1.5新增enum關鍵字的同時,也擴大了switch的語句使用范圍。Java1.5之前,switch中的值只能是簡單數據類型,比如int、byte、short、char, 有了枚舉類型之后,就可以使用枚舉類的對象了。同時在switch表達式中使用enum定義的枚舉類的對象作為表達式時, case子句可以直接使用枚舉對象的名字, 無需添加枚舉類作為限定。這樣一來,程序的控制選擇就變得更加的方便,看下面的例子:
public enum WeekDay {// 定義一周七天的枚舉類型Monday,Tuesday, Wednesday ,Thursday,Friday,Saturday,Sunday; } class Test{public static void getDay(WeekDay weekDay){switch (weekDay){case Monday:System.out.println("Today is Monday");break;case Tuesday:System.out.println("Today is Tuesday");break;case Wednesday:System.out.println("Today is Wednesday");break;case Thursday:System.out.println("Today is Thursday");break;case Friday:System.out.println("Today is Friday");break;case Saturday:System.out.println("Today is Saturday");break;case Sunday:System.out.println("Today is Sunday");break;default:System.out.println("data error");}}public static void main(String[] args) {WeekDay sunday = WeekDay.Sunday;getDay(sunday);WeekDay friday = WeekDay.Friday;getDay(friday);} }運行結果:
對于這些枚舉的日期,JVM都會在運行期構造成出一個簡單的對象實例一一對應。這些對象都有唯一的identity,類似整型數值一樣,switch語句就會根據此來identity進行執行跳轉。
5、枚舉類的線程安全問題
枚舉類天生線程就是安全的,下面我們就來進行驗證。
先寫一個簡單的枚舉類,還是以季節類為例:
public enum Season {SPRING,SUMMER,AUTUMN,WINNER; }然后我們使用反編譯,看看枚舉類代碼到底是怎么實現的,反編譯后的代碼內容如下:
public final class zzuli.edu.Season extends java.lang.Enum<zzuli.edu.Season> {public static final zzuli.edu.Season SPRING;public static final zzuli.edu.Season SUMMER;public static final zzuli.edu.Season AUTUMN;public static final zzuli.edu.Season WINNER;private static final zzuli.edu.Season[] $VALUES;public static zzuli.edu.Season[] values();public static zzuli.edu.Season valueOf(java.lang.String);private zzuli.edu.Season();static {}; }由上述代碼可知,每一個枚舉類的枚舉對象都是被public static final 進行修飾的,又因為被static修飾的屬性在類加載的時候就會被加載,而且只會被加載一次,所以枚舉類天生就是線程安全的。
6、枚舉類實現單例模式
實現單例模式的方法有很多種,但是使用枚舉類實現單例模式是最好、最安全的一種方式,這種方式也是Effective Java作者Josh Bloch 提倡的方式。因為它天生線程安全,不僅能避免多線程同步問題,而且還能防止使用反射重新創建新的對象。
使用枚舉類實現單例模式非常簡單,如下所示:
public enum EnumSingle {INSTANCE;public EnumSingle getInstance(){return INSTANCE;} }下面使用代碼進行測試,看創建的對象是否是單例:
public class Test {public static void main(String[] args) throws NoSuchMethodException {EnumSingle instance1 = EnumSingle.INSTANCE;EnumSingle instance2 = EnumSingle.INSTANCE;System.out.println(instance1==instance2);} }運行結果:
由運行結果可知,成功使用了單例模式。接下來測試使用反射能不能創建新的實例對象。
先來看一下使用反射創建實例對象newInstance方法的源碼:
[圖片上傳失敗…(image-8ba546-1633679059670)]
由newInstance方法的源碼可知,反射在通過newInstance方法創建對象時,會先檢查該類是否是枚舉類,如果是,則會拋出IllegalArgumentException(“Cannot reflectively create enum objects”)異常,導致使用反射創建對象失敗。下面我們就來測試一下:
先看一下枚舉類的源碼是有參構造函數還是無參構造函數,編譯后的源碼如下:
由源碼可知,枚舉類的構造函數為無參構造函數,下面就使用反射獲取枚舉類的無參構造函數,看使用反射是否能創建新的實例對象。
public class Test2 {public static void main(String[] args) throws Exception {EnumSingle instance1 = EnumSingle.INSTANCE;Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);} }運行結果:
由運行拋出的異常可知,并不是我們預期的newInstance方法中的IllegalArgumentException(“Cannot reflectively create enum objects”)異常,而是NoSuchMethodException異常,說明枚舉類中并沒有無參構造函數,編譯后的源碼欺騙了我們。
接著我們通過javap反編譯看一下枚舉類的代碼
[圖片上傳失敗…(image-182813-1633679059670)]
public final class zzuli.edu.enumTest.EnumSingle extends java.lang.Enum<zzuli.edu.enumTest.EnumSingle> {public static final zzuli.edu.enumTest.EnumSingle INSTANCE;private static final zzuli.edu.enumTest.EnumSingle[] $VALUES;public static zzuli.edu.enumTest.EnumSingle[] values();public static zzuli.edu.enumTest.EnumSingle valueOf(java.lang.String);private zzuli.edu.enumTest.EnumSingle();public zzuli.edu.enumTest.EnumSingle getInstance();static {}; }由上述反編譯后的枚舉類代碼可知,反編譯后的枚舉類中存在的仍然是無參構造函數,說明反編譯后的代碼仍然騙了我們。下面我們就使用更專業的工具jad來進行反編譯。
使用jad反編譯后的枚舉類源碼如下所示:
public final class EnumSingle extends Enum {public static EnumSingle[] values(){return (EnumSingle[])$VALUES.clone();}public static EnumSingle valueOf(String name){return (EnumSingle)Enum.valueOf(zzuli/edu/enumTest/EnumSingle, name);}private EnumSingle(String s, int i){super(s, i);}public EnumSingle getInstance(){return INSTANCE;}public static final EnumSingle INSTANCE;private static final EnumSingle $VALUES[];static {INSTANCE = new EnumSingle("INSTANCE", 0);$VALUES = (new EnumSingle[] {INSTANCE});} }由jad反編譯后的源碼可知,枚舉類的構造函數為有參構造函數 EnumSingle(String s, int i),并且有兩個參數。
下面我們就使用反射獲取枚舉類的有參構造函數,看使用反射是否能創建新的實例對象。
public class Test3 {public static void main(String[] args) throws Exception {EnumSingle instance1 = EnumSingle.INSTANCE;Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);declaredConstructor.setAccessible(true);EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}運行結果:
由運行結果可知,與我們預期的異常一樣,拋出了IllegalArgumentException(“Cannot reflectively create enum objects”)異常。
 此時說明使用枚舉類實現單例模式是十分安全的,使用反射進行暴力破解也不能創建新的對象。
總結
以上是生活随笔為你收集整理的你连简单的枚举类都不知道,还敢说自己会Java???滚出我的公司的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 华为二面!!!面试官直接问我Java中到
- 下一篇: 长安/华为/宁德时代联合打造!阿维塔11
