掌握Java枚舉這幾個知識點,日常開發就夠了

整理了Java枚舉的相關知識,算是比較基礎的,希望大家一起學習進步。

掌握Java枚舉這幾個知識點,日常開發就夠了


一、枚舉類型是什麼?

JDK5引入了一種新特性,關鍵字enum可以將一組具名的值的有限集合創建為一種新的類型,而這些具名的值可以作為常規的程序組件使用,這就是枚舉類型。

一個枚舉的簡單例子

<code>enum SeasonEnum {
SPRING,SUMMER,FALL,WINTER;
}/<code>

二、 枚舉類的常用方法

Enum常用方法有以下幾種:

  • name(); 返回enum實例聲明時的名字。
  • ordinal(); 返回一個int值,表示enum實例在聲明的次序。
  • equals(); 返回布爾值,enum實例判斷相等
  • compareTo() 比較enum實例與指定對象的順序
  • values(); 返回enum實例的數組
  • valueOf(String name) 由名稱獲取枚舉類中定義的常量

直接看例子吧:

<code>enum Shrubbery {

GROUND,CRAWLING, HANGING

}

public class EnumClassTest {

public static void main(String[] args) {

//values 返回enum實例的數組

for (Shrubbery temp : Shrubbery.values()) {

// name 返回實例enum聲明的名字

System.out.println(temp.name() + " ordinal is " + temp.ordinal() + " ,equal result is " +

Shrubbery.CRAWLING.equals(temp) + ",compare result is " + Shrubbery.CRAWLING.compareTo(temp));

}

//由名稱獲取枚舉類中定義的常量值

System.out.println(Shrubbery.valueOf("CRAWLING"));

}

}/<code>

運行結果:

<code>GROUND ordinal is 0 ,equal result is false,compare result is 1

CRAWLING ordinal is 1 ,equal result is true,compare result is 0

HANGING ordinal is 2 ,equal result is false,compare result is -1

CRAWLING三、枚舉類的真面目/<code>

枚舉類型到底是什麼類呢?我們新建一個簡單枚舉例子,看看它的廬山真面目。如下:

<code>public enum Shrubbery {
GROUND,CRAWLING, HANGING
}/<code>

使用javac編譯上面的枚舉類,可得Shrubbery.class文件。

<code>javac Shrubbery.java/<code>

再用javap命令,反編譯得到字節碼文件。如:執行 javapShrubbery.class可到以下字節碼文件。

<code>Compiled from "Shrubbery.java"

public final class enumtest.Shrubbery extends java.lang.Enum<enumtest.shrubbery> {

public static final enumtest.Shrubbery GROUND;

public static final enumtest.Shrubbery CRAWLING;

public static final enumtest.Shrubbery HANGING;

public static enumtest.Shrubbery[] values();

public static enumtest.Shrubbery valueOf(java.lang.String);

static {};

}/<enumtest.shrubbery>/<code>

從字節碼文件可以發現:

  • Shrubbery枚舉變成了一個final修飾的類,也就是說,它不能被繼承啦。
  • Shrubbery是java.lang.Enum的子類。
  • Shrubbery定義的枚舉值都是public static final修飾的,即都是靜態常量。

為了看得更仔細,javap反編譯加多個參數-c,執行如下命令:

<code>javap -c Shrubbery.class/<code>

靜態代碼塊的字節碼文件如下:

<code>static {};

Code:

0: new #4 // class enumtest/Shrubbery

3: dup

4: ldc #7 // String GROUND

6: iconst_0

7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V

10: putstatic #9 // Field GROUND:Lenumtest/Shrubbery;

13: new #4 // class enumtest/Shrubbery

16: dup

17: ldc #10 // String CRAWLING

19: iconst_1

20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V

23: putstatic #11 // Field CRAWLING:Lenumtest/Shrubbery;

26: new #4 // class enumtest/Shrubbery

29: dup

30: ldc #12 // String HANGING

32: iconst_2

33: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V

36: putstatic #13 // Field HANGING:Lenumtest/Shrubbery;

39: iconst_3

40: anewarray #4 // class enumtest/Shrubbery

43: dup

44: iconst_0

45: getstatic #9 // Field GROUND:Lenumtest/Shrubbery;

48: aastore

49: dup

50: iconst_1

51: getstatic #11 // Field CRAWLING:Lenumtest/Shrubbery;

54: aastore

55: dup

56: iconst_2

57: getstatic #13 // Field HANGING:Lenumtest/Shrubbery;

60: aastore

61: putstatic #1 // Field $VALUES:[Lenumtest/Shrubbery;

64: return

}
/<init>/<init>/<init>/<code>
  • 0-39行實例化了Shrubbery枚舉類的GROUND,CRAWLING, HANGING;
  • 40-64為創建Shrubbery[]數組$VALUES,並將上面的三個實例化對象放入數組的操作。
  • 因此,枚舉類方法values()返回enum枚舉實例的數組是不是豁然開朗啦。

四、枚舉類的優點

枚舉類有什麼優點呢?就是我們為什麼要選擇使用枚舉類呢?因為它可以增強

代碼的可讀性,可維護性,同時,它也具有安全性。

枚舉類可以增強可讀性、可維護性

假設現在有這樣的業務場景:訂單完成後,通知買家評論。很容易有以下代碼:

<code>//訂單已完成
if(3==orderStatus){
//do something
}/<code>

很顯然,這段代碼出現了魔法數,如果你沒寫註釋,誰知道3表示訂單什麼狀態呢,不僅閱讀起來比較困難,維護起來也很蛋疼?如果使用枚舉類呢,如下:

<code>public enum OrderStatusEnum {

UNPAID(0, "未付款"), PAID(1, "已付款"), SEND(2, "已發貨"), FINISH(3, "已完成"),;

private int index;

private String desc;

public int getIndex() {

return index;

}

public String getDesc() {

return desc;


}

OrderStatusEnum(int index, String desc) {

this.index = index;

this.desc = desc;

}

}

//訂單已完成

if(OrderStatusEnum.FINISH.getIndex()==orderStatus){

//do something

}/<code>

可見,枚舉類讓這段代碼可讀性更強,也比較好維護,後面加個新的訂單狀態,直接添加多一種枚舉狀態就可以了。有些朋友認為, publicstaticfinalint這種靜態常量也可以實現該功能呀,如下:

<code>public class OrderStatus {

//未付款

public static final int UNPAID = 0;

public static final int PAID = 1;

public static final int SENDED = 2;

public static final int FINISH = 3;

}

//訂單已完成

if(OrderStatus.FINISH==orderStatus){

//do something

}/<code>

當然,靜態常量這種方式實現,可讀性是沒有任何問題的,日常工作中代碼這樣寫也無可厚非。但是,定義int值相同的變量,容易混淆,如你定義 PAID和 SENDED狀態都是2,編譯器是不會報錯的。

因此,枚舉類第一個優點就是可讀性,可維護性都不錯,所以推薦。

枚舉類安全性

除了可讀性、可維護性外,枚舉類還有個巨大的優點,就是安全性。

從上一節枚舉類字節碼分析,我們知道:

  • 一個枚舉類是被final關鍵字修飾的,不能被繼承。
  • 並且它的變量都是public static final修飾的,都是靜態變量。

當一個Java類第一次被真正使用到的時候靜態資源被初始化、Java類的加載和初始化過程都是線程安全的。

五、枚舉的常見用法

enum組織常量

在JDK5之前,常量定義都是這樣,先定義一個類或者接口,屬性類型都是public static final...,有了枚舉之後,可以把常量組織到枚舉類了,如下:

<code>enum SeasonEnum {
SPRING,SUMMER,FALL,WINTER,;
}/<code>

enum與switch 環環相扣

一般來說,switch-case中只能使用整數值,但是枚舉實例天生就具備整數值的次序,因此,在switch語句中是可以使用enum的,如下:

<code>enum OrderStatusEnum {
UNPAID, PAID, SEND, FINISH
}
public class OrderStatusTest {
public static void main(String[] args) {
changeByOrderStatus(OrderStatusEnum.FINISH);
}

static void changeByOrderStatus(OrderStatusEnum orderStatusEnum) {
switch (orderStatusEnum) {
case UNPAID:
System.out.println("老闆,你下單了,趕緊付錢吧");
break;
case PAID:
System.out.println("我已經付錢啦");
break;
case SENDED:
System.out.println("已發貨");
break;
case FINISH:
System.out.println("訂單完成啦");
break;
}
}

}/<code>

在日常開發中,enum與switch一起使用,會讓你的代碼可讀性更好哦。

向枚舉中添加新的方法

可以向枚舉類添加新方法的,如get方法,普通方法等,以下是日常工作最常用的一種枚舉寫法:

<code>public enum OrderStatusEnum {
UNPAID(0, "未付款"), PAID(1, "已付款"), SENDED(2, "已發貨"), FINISH(3, "已完成"),;

//成員變量
private int index;
private String desc;

//get方法
public int getIndex() {
return index;
}

public String getDesc() {
return desc;
}

//構造器方法
OrderStatusEnum(int index, String desc) {
this.index = index;
this.desc = desc;
}

//普通方法
public static OrderStatusEnum of(int index){
for (OrderStatusEnum temp : values()) {
if (temp.getIndex() == index) {
return temp;
}
}
return null;
}
}/<code>

枚舉實現接口

所有枚舉類都繼承於java.lang.Enum,所以枚舉不能再繼承其他類了。但是枚舉可以實現接口呀,這給枚舉增添了不少色彩。如下:

<code>public interface ISeasonBehaviour {

void showSeasonBeauty();

String getSeasonName();
}
public enum SeasonEnum implements ISeasonBehaviour {
SPRING(1,"春天"),SUMMER(2,"夏天"),FALL(3,"秋天"),WINTER(4,"冬天"),
;

private int index;
private String name;

SeasonEnum(int index, String name) {
this.index = index;
this.name = name;
}

public int getIndex() {
return index;
}
public String getName() {
return name;
}

//接口方法
@Override
public void showSeasonBeauty() {
System.out.println("welcome to " + this.name);
}

//接口方法
@Override
public String getSeasonName() {
return this.name;
}
}/<code>

使用接口組織枚舉

<code>public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}/<code>

六、枚舉類比較是用==還是equals?

先看一個例子,如下:

<code>public class EnumTest {
public static void main(String[] args) {

Shrubbery s1 = Shrubbery.CRAWLING;
Shrubbery s2 = Shrubbery.GROUND;
Shrubbery s3 = Shrubbery.CRAWLING;

System.out.println("s1==s2,result: " + (s1 == s2));
System.out.println("s1==s3,result: " + (s1 == s3));
System.out.println("Shrubbery.CRAWLING.equals(s1),result: "+Shrubbery.CRAWLING.equals(s1));
System.out.println("Shrubbery.CRAWLING.equals(s2),result: "+Shrubbery.CRAWLING.equals(s2));

}
}/<code>

運行結果:

<code>s1==s2,result: false
s1==s3,result: true
Shrubbery.CRAWLING.equals(s1),result: true
Shrubbery.CRAWLING.equals(s2),result: false/<code>

可以發現不管用==還是equals,都是可以的。其實枚舉的equals方法,就是用==比較的,如下:

<code>public final boolean equals(Object other) {
return this==other;
}/<code>

七、枚舉實現的單例

effective java提過,最佳的單例實現模式就是枚舉模式。單例模式的實現有好幾種方式,為什麼是枚舉實現的方式最佳呢?

因為枚舉實現的單例有以下優點:

  • 枚舉單例寫法簡單
  • 枚舉可解決線程安全問題
  • 枚舉可解決反序列化會破壞單例的問題

一個枚舉單例demo如下:

<code>public class SingletonEnumTest {
public enum SingletonEnum {
INSTANCE,;
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

public static void main(String[] args) {
SingletonEnum.INSTANCE.setName("jay@huaxiao");
System.out.println(SingletonEnum.INSTANCE.getName());
}
}/<code>

有關於枚舉實現單例,想深入瞭解的朋友可以看Hollis大神這篇文章,寫得真心好!為什麼我牆裂建議大家使用枚舉來實現單例。

八、EnumSet 和EnumMap

EnumSet

先來看看EnumSet的繼承體系圖

掌握Java枚舉這幾個知識點,日常開發就夠了

顯然,EnumSet也實現了set接口,相比於HashSet,它有以下優點:

  • 消耗較少的內存
  • 效率更高,因為是位向量實現的。
  • 可以預測的遍歷順序(enum常量的聲明順序)
  • 拒絕加null

EnumSet就是set的高性能實現,它的要求就是存放必須是同一枚舉類型。EnumSet的常用方法:

  • allof() 創建一個包含指定枚舉類裡所有枚舉值的EnumSet集合
  • range() 獲取某個範圍的枚舉實例
  • of() 創建一個包括參數中所有枚舉元素的EnumSet集合
  • complementOf() 初始枚舉集合包括指定枚舉集合的補集

看實例,最實際:

<code>public class EnumTest {
public static void main(String[] args) {
// Creating a set

EnumSet<seasonenum> set1, set2, set3, set4;

// Adding elements
set1 = EnumSet.of(SeasonEnum.SPRING, SeasonEnum.FALL, SeasonEnum.WINTER);
set2 = EnumSet.complementOf(set1);
set3 = EnumSet.allOf(SeasonEnum.class);
set4 = EnumSet.range(SeasonEnum.SUMMER,SeasonEnum.WINTER);
System.out.println("Set 1: " + set1);
System.out.println("Set 2: " + set2);
System.out.println("Set 3: " + set3);
System.out.println("Set 4: " + set4);
}
}/<seasonenum>/<code>

輸出結果:

<code>Set 1: [SPRING, FALL, WINTER]
Set 2: [SUMMER]
Set 3: [SPRING, SUMMER, FALL, WINTER]
Set 4: [SUMMER, FALL, WINTER]/<code>

EnumMap

EnumMap的繼承體系圖如下:

掌握Java枚舉這幾個知識點,日常開發就夠了

EnumMap也實現了Map接口,相對於HashMap,它也有這些優點:

  • 消耗較少的內存
  • 效率更高
  • 可以預測的遍歷順序
  • 拒絕null

EnumMap就是map的高性能實現。 它的常用方法跟HashMap是一致的,唯一約束是枚舉相關。

看實例,最實際:

<code>public class EnumTest {
public static void main(String[] args) {
Map<seasonenum> map = new EnumMap<>(SeasonEnum.class);
map.put(SeasonEnum.SPRING, "春天");
map.put(SeasonEnum.SUMMER, "夏天");
map.put(SeasonEnum.FALL, "秋天");
map.put(SeasonEnum.WINTER, "冬天");
System.out.println(map);
System.out.println(map.get(SeasonEnum.SPRING));
}
}/<seasonenum>/<code>

運行結果

<code>{SPRING=春天, SUMMER=夏天, FALL=秋天, WINTER=冬天}春天/<code>

九、待更新

有關於枚舉關鍵知識點,親愛的朋友,你有沒有要補充的呢?


分享到:


相關文章: