枚舉類型enum並不是面試裏面的一個常考點,但是爲什麼要給枚舉寫一篇博客呢,因爲我簡歷上自己在項目中運用了枚舉,11月11號在面試的時候,讓我寫一個枚舉,我竟然沒有寫出來,當時就覺得很不好意思,所以這裏將自己對枚舉的重新認識記錄一下。
一.最簡單的枚舉例子
enum Status{
NORMAL(1),STOP(0),DELETE(-1);
public Integer getStatusInt() {
return statusInt;
}
private Integer statusInt;
Status(Integer statusInt){
this.statusInt = statusInt;
}
}
寫枚舉需要注意的地方就是枚舉的關鍵字enum,然後再是首先要申明的枚舉常量,在申明的枚舉常量之前不能有任何代碼,常量一般用大寫字母來寫,枚舉常量之間用逗號分割,最後用分號收尾。用這份代碼祭奠我在面試中沒有回答上的這道題。
二.枚舉的好處
枚舉在工程中隨處可進,爲什麼要用枚舉呢?枚舉是將一些離散值集中起來,用同一個枚舉類型來封裝他們,做到一處定義,處處使用。
1.安全性
安全性體現在枚舉將想要的有限的離散包含進來了,如果要是用這些值只能從枚舉裏面取,外面隨便定義的值不被接受。
2.易於維護,可讀性強
NORMAL(1),STOP(0),DELETE(-1);
給學生賦狀態值
Student student = new Student();
//通過枚舉賦值
student.setStatus(Status.DELETE.getStatusInt());
//通過直接賦值
student.setStatus(-1);
可以看出一個是直接通過枚舉賦值,一個是直接賦值,雖然枚舉賦值沒有直接賦值那麼簡潔,但是我們一眼就可以看出用枚舉賦值是什麼意思,而直接賦值的話對於剛接手的人來說沒有註解的話就是一頭霧水。如果那天這個1被用作表示其他狀態值了,還得查詢整個項目是修改這個值,如果用的是枚舉的話,就只需修改枚舉這一塊地方就好了
枚舉相對於常量的好處
有很多時候枚舉和常量可以相互替換,但是枚舉的話相比常量會帶有更多的信息,我這裏說的常量類型是指基礎類型和String,枚舉裏面可以設置多個屬性,包含的信息量要多一些,有人可能會疑問我也可以將常量設置成對象,對象裏面帶多個類型不就好了嗎?但是這樣的話雖然不能改變常量的引用對象,但是確能改變引用對象的值,所以說也不是很安全。
三.枚舉的基本介紹
創建枚舉類,其實是在背後偷偷的幫我們繼承了Enum這個抽象類的,這個抽象類是我們可以用到的
將網上其他人寫的代碼拿來一用一下
類聚類型定義
//定義枚舉類型
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
對Day進行反編譯後會看到這樣一個類:
//反編譯Day.class
final class Day extends Enum
{
//編譯器爲我們添加的靜態的values()方法
public static Day[] values()
{
return (Day[])$VALUES.clone();
}
//編譯器爲我們添加的靜態的valueOf()方法,注意間接調用了Enum也類的valueOf方法
public static Day valueOf(String s)
{
return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
}
//私有構造函數
private Day(String s, int i)
{
super(s, i);
}
//前面定義的7種枚舉實例
public static final Day MONDAY;
public static final Day TUESDAY;
public static final Day WEDNESDAY;
public static final Day THURSDAY;
public static final Day FRIDAY;
public static final Day SATURDAY;
public static final Day SUNDAY;
private static final Day $VALUES[];
static
{
//實例化枚舉實例
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
WEDNESDAY = new Day("WEDNESDAY", 2);
THURSDAY = new Day("THURSDAY", 3);
FRIDAY = new Day("FRIDAY", 4);
SATURDAY = new Day("SATURDAY", 5);
SUNDAY = new Day("SUNDAY", 6);
$VALUES = (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
可以看到枚舉類其實是一個final類型的普通類,只是這個類繼承了Enum抽象類,並且有一個私有的構造方法,在靜態代碼塊裏就實例化了這些常量。
看一下Enum這個類給提供了哪些常用方法
返回類型 | 方法名稱 | 方法說明 |
---|---|---|
int |
compareTo(E o) |
比較此枚舉與指定對象的順序 |
boolean |
equals(Object other) |
當指定對象等於此枚舉常量時,返回 true。 |
Class<?> |
getDeclaringClass() |
返回與此枚舉常量的枚舉類型相對應的 Class 對象 |
String |
name() |
返回此枚舉常量的名稱,在其枚舉聲明中對其進行聲明 |
int |
ordinal() |
返回枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量序數爲零) |
String |
toString() |
返回枚舉常量的名稱,它包含在聲明中 |
static<T extends Enum<T>> T |
static valueOf(Class<T> enumType, String name) |
|
其中還有一個values,用於遍歷枚舉的,只是這個方法是在編譯的時候纔會出現在class文件裏,不會出現java類中,values用戶遍歷定義的枚舉類型所有常量。values返回的是一個數組。
其中ordinal返回的序數是根據常量擺放的順序值返回的,不是根據你定義的時候傳入值的大小擺放的,如果常量擺放的順序發生變化,這個值也也會發生變化,定義的常量放在第二位,那麼返回的就是1。
public enum EnumWeek {
SUN("7","星期日"),
Mon("1","星期一"),
Tue("2","星期二"),
WIN("3","星期三"),
THU("4","星期四"),
FRI("5","星期五"),
SAT("6","星期六");
private String no;
private String des;
EnumWeek(String no,String des){
this.no=no;
this.des=des;
}
@Override
public String toString(){
return this.no+";"+this.des;
}
public static void main(String[] args) {
System.out.println(FRI.name());
System.out.println(FRI.toString());//和valueOf輸出是一樣的
System.out.println(EnumWeek.valueOf(SUN.name()));
System.out.println(SUN.compareTo(FRI));
System.out.println(SUN.ordinal());
System.out.println("========遍歷輸出=======");
for(EnumWeek enumWeek:EnumWeek.values()){
System.out.println(enumWeek.toString());
}
}
}
輸出結果:
FRI
5;星期五
7;星期日
-5
0
========遍歷輸出=======
7;星期日
1;星期一
2;星期二
3;星期三
4;星期四
5;星期五
6;星期六
四.枚舉的基本用法
1.作爲一個存儲離散值的封裝類,定義好後,處處使用,這也是枚舉最常用的用法。
2.作爲switch的參數,switch的參數不僅可以是整數,也可以是字符串,還可以是枚舉類型。
enum Status{
NORMAL(1),STOP(0),DELETE(-1);
public Integer getStatusInt() {
return statusInt;
}
private Integer statusInt;
Status(Integer statusInt){
this.statusInt = statusInt;
}
public static void switchTest(Status status){
switch (status){
case STOP:{
System.out.println("暫停");break;
}
case DELETE:{
System.out.println("刪除");break;
}
case NORMAL:{
System.out.println("正常");break;
}
}
}
public static void main(String[] args) {
switchTest(Status.DELETE);
}
}
最後輸出:刪除
3.創建單例
單例模式最重要的就是一個整個堆中只有一個這樣的對象,飢漢模式和雙重檢測都能做到在多線程的情況下創建一個對象,但是當我們使用反射的時候,是可以破壞這個單例模式的,會讓單例模式失效。但是使用枚舉創建單例的化就不會出現被破壞的情況
下面可以對比看一下
class SingleDoubleCheck{
public static volatile SingleDoubleCheck singleDoubleCheck = null;
private SingleDoubleCheck(){};
public static SingleDoubleCheck getSingleDoubleCheck(){
if(singleDoubleCheck==null){
synchronized (SingleDoubleCheck.class){
if(singleDoubleCheck==null){
singleDoubleCheck = new SingleDoubleCheck();
}
}
}
return singleDoubleCheck;
}
}
enum EnumSingle{
ISTANCE;
/**
* 如果有什麼屬性的化,在下面定義就好了,我這個相當於是不帶任何屬性的
*/
}
class Test{
public static void main(String[] args) throws Exception{
SingleDoubleCheck singleDoubleCheck = SingleDoubleCheck.getSingleDoubleCheck();
Class<SingleDoubleCheck> singleDoubleCheckClass = SingleDoubleCheck.class;
Constructor c0= singleDoubleCheckClass.getDeclaredConstructor();
c0.setAccessible(true);
SingleDoubleCheck singleDoubleCheckTwo=(SingleDoubleCheck)c0.newInstance();
System.out.println("SingleDoubleCheck對象:"+singleDoubleCheck+" ;"+singleDoubleCheckTwo.toString());
System.out.println(singleDoubleCheck==singleDoubleCheckTwo);
System.out.println("=================分割線===================");
EnumSingle enumSingle = EnumSingle.ISTANCE;
Class<EnumSingle> enumSingleClass = EnumSingle.class;
Constructor c1 = enumSingleClass.getDeclaredConstructor();
c1.setAccessible(true);
EnumSingle enumSingleTwo = (EnumSingle)c1.newInstance();
System.out.println("EnumSingle對象:"+enumSingle+" ;"+enumSingleTwo.toString());
System.out.println(enumSingle==enumSingleTwo);
}
}
最後輸出:可以看出SingleDoubleCheck雖然是雙重檢測單例模式,但是我們還可以通過反射獲取到實例,這就破壞了這個單例模式,而枚舉的化如果想用反射來實例是會報錯的
SingleDoubleCheck對象:com.example.findwork.normal.SingleDoubleCheck@7d417077 ;com.example.findwork.normal.SingleDoubleCheck@7dc36524
false
=================分割線===================
Exception in thread "main" java.lang.NoSuchMethodException: com.example.findwork.normal.EnumSingle.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.example.findwork.normal.Test.main(EnumWeek.java:141)
五.枚舉的集合類型
1.EnumMap
EnumMap存儲的都是key是枚舉類型的值。
class EnumMapTest{
public static void main(String[] args) {
Map<Status,String> enumMap = new EnumMap(Status.class);
enumMap.put(Status.DELETE,"刪除狀態");
enumMap.put(Status.STOP,"暫停狀態");
enumMap.put(Status.NORMAL,"正常狀態");
for(Map.Entry<Status,String> entry:enumMap.entrySet()){
System.out.println("輸出結果:"+entry.toString());
}
}
}
輸出結果:NORMAL=正常狀態
輸出結果:STOP=暫停狀態
輸出結果:DELETE=刪除狀態
既然有HashMap,爲什麼要用EnumMap,這是因爲EnumMap裏面存儲結構和HashMap不一樣,效率更高效一些,EnumMap裏面是兩個數組,一個存儲Enum類型的key值,一個存儲value值,沒有鏈表更沒有紅黑樹,這是因爲在實例化EnumMap會傳入Enum class對象,這樣EnumMap就知道enum有多少常量,有哪些枚舉常量,這樣就可以實例話key數組,存儲值的時候就可以根據enum的序列位知道這個值存在數組的哪一個位置了。