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

前言

春節來臨之際,祝你們新年快樂。整理了Java枚舉的相關知識,算是比較基礎的,但願你們一塊兒學習進步。 java

本章節全部代碼demo已上傳githubgit

1、枚舉類型是什麼?

JDK5引入了一種新特性,關鍵字enum能夠將一組具名的值的有限集合建立爲一種新的類型,而這些具名的值能夠做爲常規的程序組件使用,這就是枚舉類型。github

一個枚舉的簡單例子編程

enum SeasonEnum {
    SPRING,SUMMER,FALL,WINTER;
}
複製代碼

2、 枚舉類的經常使用方法

Enum經常使用方法有如下幾種:數組

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

直接看例子吧:安全

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"));
    }
}

複製代碼

運行結果:bash

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
複製代碼

3、枚舉類的真面目

枚舉類型究竟是什麼類呢?咱們新建一個簡單枚舉例子,看看它的廬山真面目。以下:ide

public enum Shrubbery {
    GROUND,CRAWLING, HANGING
}
複製代碼

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

javac Shrubbery.java
複製代碼

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

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 {};
}
複製代碼

從字節碼文件能夠發現:

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

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

javap -c Shrubbery.class
複製代碼

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

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
}
複製代碼
  • 0-39行實例化了Shrubbery枚舉類的GROUND,CRAWLING, HANGING;
  • 40-64爲建立Shrubbery[]數組$VALUES,並將上面的三個實例化對象放入數組的操做。
  • 所以,枚舉類方法values()返回enum枚舉實例的數組是否是豁然開朗啦。

4、枚舉類的優勢

枚舉類有什麼優勢呢?就是咱們爲何要選擇使用枚舉類呢?由於它能夠加強代碼的可讀性,可維護性,同時,它也具備安全性。

枚舉類能夠加強可讀性、可維護性

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

//訂單已完成
if(3==orderStatus){
//do something    
}
複製代碼

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

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
 }
複製代碼

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

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
 }
複製代碼

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

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

枚舉類安全性

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

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

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

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

5、枚舉的常見用法

enum組織常量

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

enum SeasonEnum {
    SPRING,SUMMER,FALL,WINTER,;
}
複製代碼

enum與switch 環環相扣

通常來講,switch-case中只能使用整數值,可是枚舉實例天生就具有整數值的次序,所以,在switch語句中是可使用enum的,以下:

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;
        }
    }
}

複製代碼

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

向枚舉中添加新的方法

能夠向枚舉類添加新方法的,如get方法,普通方法等,如下是平常工做最經常使用的一種枚舉寫法:

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;
    }
}

複製代碼

枚舉實現接口

全部枚舉類都繼承於java.lang.Enum,因此枚舉不能再繼承其餘類了。可是枚舉能夠實現接口呀,這給枚舉增添了很多色彩。以下:

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;
    }
}
複製代碼

使用接口組織枚舉

public interface Food {
    enum Coffee implements Food{  
        BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO  
    }  
    enum Dessert implements Food{  
        FRUIT, CAKE, GELATO  
    }  
}
複製代碼

6、枚舉類比較是用==仍是equals?

先看一個例子,以下:

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));

    }
}
複製代碼

運行結果:

s1==s2,result: false
s1==s3,result: true
Shrubbery.CRAWLING.equals(s1),result: true
Shrubbery.CRAWLING.equals(s2),result: false
複製代碼

能夠發現無論用==仍是equals,都是能夠的。其實枚舉的equals方法,就是用==比較的,以下:

public final boolean equals(Object other) {
    return this==other;
}
複製代碼

7、枚舉實現的單例

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

由於枚舉實現的單例有如下優勢:

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

一個枚舉單例demo以下:

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());
    }
}
複製代碼

有關於枚舉實現單例,想深刻了解的朋友能夠看Hollis大神這篇文章,寫得真心好!爲何我牆裂建議你們使用枚舉來實現單例。

8、EnumSet 和EnumMap

EnumSet

先來看看EnumSet的繼承體系圖

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

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

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

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

看實例,最實際:

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);
    }
}
複製代碼

輸出結果:

Set 1: [SPRING, FALL, WINTER]
Set 2: [SUMMER]
Set 3: [SPRING, SUMMER, FALL, WINTER]
Set 4: [SUMMER, FALL, WINTER]
複製代碼

EnumMap

EnumMap的繼承體系圖以下:

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

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

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

看實例,最實際:

public class EnumTest {
    public static void main(String[] args) {
        Map<SeasonEnum, String> 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));
    }
}
複製代碼

運行結果

{SPRING=春天, SUMMER=夏天, FALL=秋天, WINTER=冬天}
春天
複製代碼

9、待更新

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

參考與感謝

我的公衆號

  • 若是你是個愛學習的好孩子,能夠關注我公衆號,一塊兒學習討論。
  • 若是你以爲本文有哪些不正確的地方,能夠評論,也能夠關注我公衆號,私聊我,你們一塊兒學習進步哈。
相關文章
相關標籤/搜索