溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java枚舉類知識詳細(xì)講解

發(fā)布時間:2021-09-01 11:01:45 來源:億速云 閱讀:147 作者:chen 欄目:編程語言

本篇內(nèi)容主要講解“Java枚舉類知識詳細(xì)講解”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Java枚舉類知識詳細(xì)講解”吧!

枚舉(enum)類型是Java 5新增的特性,它是一種新的類型,允許用常量來表示特定的數(shù)據(jù)片斷,而且全部都以類型安全的形式來表示。

初探枚舉類

在程序設(shè)計中,有時會用到由若干個有限數(shù)據(jù)元素組成的集合,如一周內(nèi)的星期一到星期日七個數(shù)據(jù)元素組成的集合,由三種顏色紅、黃、綠組成的集合,一個工作班組內(nèi)十個職工組成的集合等等,程序中某個變量取值僅限于集合中的元素。此時,可將這些數(shù)據(jù)集合定義為枚舉類型。

因此,枚舉類型是某類數(shù)據(jù)可能取值的集合,如一周內(nèi)星期可能取值的集合為:
  { Sun,Mon,Tue,Wed,Thu,Fri,Sat}
  該集合可定義為描述星期的枚舉類型,該枚舉類型共有七個元素,因而用枚舉類型定義的枚舉變量只能取集合中的某一元素值。由于枚舉類型是導(dǎo)出數(shù)據(jù)類型,因此,必須先定義枚舉類型,然后再用枚舉類型定義枚舉型變量。  

enum <枚舉類型名> 
  { <枚舉元素表> };
  
  其中:關(guān)鍵詞enum表示定義的是枚舉類型,枚舉類型名由標(biāo)識符組成,而枚舉元素表由枚舉元素或枚舉常量組成。例如: 
  
enum weekdays 
  { Sun,Mon,Tue,Wed,Thu,Fri,Sat };
  定義了一個名為 weekdays的枚舉類型,它包含七個元素:Sun、Mon、Tue、Wed、Thu、Fri、Sat。

在編譯器編譯程序時,給枚舉類型中的每一個元素指定一個整型常量值(也稱為序號值)。若枚舉類型定義中沒有指定元素的整型常量值,則整型常量值從0開始依次遞增,因此,weekdays枚舉類型的七個元素Sun、Mon、Tue、Wed、Thu、Fri、Sat對應(yīng)的整型常量值分別為0、1、2、3、4、5、6。
  注意:在定義枚舉類型時,也可指定元素對應(yīng)的整型常量值。

例如,描述邏輯值集合{TRUE、FALSE}的枚舉類型boolean可定義如下:
enum boolean 
  { TRUE=1 ,FALSE=0 };
該定義規(guī)定:TRUE的值為1,而FALSE的值為0。
  
而描述顏色集合{red,blue,green,black,white,yellow}的枚舉類型colors可定義如下:
enum colors 
  {red=5,blue=1,green,black,white,yellow};
  該定義規(guī)定red為5 ,blue為1,其后元素值從2 開始遞增加1。green、black、white、yellow的值依次為2、3、4、5。

  此時,整數(shù)5將用于表示二種顏色red與yellow。通常兩個不同元素取相同的整數(shù)值是沒有意義的。枚舉類型的定義只是定義了一個新的數(shù)據(jù)類型,只有用枚舉類型定義枚舉變量才能使用這種數(shù)據(jù)類型。

枚舉類-語法

enum 與 class、interface 具有相同地位;
可以繼承多個接口;
可以擁有構(gòu)造器、成員方法、成員變量;
1.2 枚舉類與普通類不同之處

默認(rèn)繼承 java.lang.Enum 類,所以不能繼承其他父類;其中 java.lang.Enum 類實現(xiàn)了 java.lang.Serializable 和 java.lang.Comparable 接口;

使用 enum 定義,默認(rèn)使用 final 修飾,因此不能派生子類;

構(gòu)造器默認(rèn)使用 private 修飾,且只能使用 private 修飾;

枚舉類所有實例必須在第一行給出,默認(rèn)添加 public static final 修飾,否則無法產(chǎn)生實例;

枚舉類的具體使用

這部分內(nèi)容參考 https://blog.csdn.net/qq_27093465/article/details/52180865

常量
public class 常量 {
}
enum Color {
    Red, Green, Blue, Yellow
}
switch

JDK1.6之前的switch語句只支持int,char,enum類型,使用枚舉,能讓我們的代碼可讀性更強。

public static void showColor(Color color) {
        switch (color) {
            case Red:
                System.out.println(color);
                break;
            case Blue:
                System.out.println(color);
                break;
            case Yellow:
                System.out.println(color);
                break;
            case Green:
                System.out.println(color);
                break;
        }
    }
向枚舉中添加新方法

如果打算自定義自己的方法,那么必須在enum實例序列的最后添加一個分號。而且 Java 要求必須先定義 enum 實例。

enum Color {
    //每個顏色都是枚舉類的一個實例,并且構(gòu)造方法要和枚舉類的格式相符合。
    //如果實例后面有其他內(nèi)容,實例序列結(jié)束時要加分號。
    Red("紅色", 1), Green("綠色", 2), Blue("藍色", 3), Yellow("黃色", 4);
    String name;
    int index;
    Color(String name, int index) {
        this.name = name;
        this.index = index;
    }
    public void showAllColors() {
        //values是Color實例的數(shù)組,在通過index和name可以獲取對應(yīng)的值。
        for (Color color : Color.values()) {
            System.out.println(color.index + ":" + color.name);
        }
    }
}
覆蓋枚舉的方法

所有枚舉類都繼承自Enum類,所以可以重寫該類的方法
下面給出一個toString()方法覆蓋的例子。

@Override
public String toString() {
    return this.index + ":" + this.name;
}
實現(xiàn)接口

所有的枚舉都繼承自java.lang.Enum類。由于Java 不支持多繼承,所以枚舉對象不能再繼承其他類。

enum Color implements Print{
    @Override
    public void print() {
        System.out.println(this.name);
    }
}
使用接口組織枚舉

搞個實現(xiàn)接口,來組織枚舉,簡單講,就是分類吧。如果大量使用枚舉的話,這么干,在寫代碼的時候,就很方便調(diào)用啦。

public class 用接口組織枚舉 {
    public static void main(String[] args) {
        Food cf = chineseFood.dumpling;
        Food jf = Food.JapaneseFood.fishpiece;
        for (Food food : chineseFood.values()) {
            System.out.println(food);
        }
        for (Food food : Food.JapaneseFood.values()) {
            System.out.println(food);
        }
    }
}
interface Food {
    enum JapaneseFood implements Food {
        suse, fishpiece
    }
}
enum chineseFood implements Food {
    dumpling, tofu
}
枚舉類集合

java.util.EnumSet和java.util.EnumMap是兩個枚舉集合。EnumSet保證集合中的元素不重復(fù);EnumMap中的 key是enum類型,而value則可以是任意類型。

EnumSet在JDK中沒有找到實現(xiàn)類,這里寫一個EnumMap的例子

public class 枚舉類集合 {
    public static void main(String[] args) {
        EnumMap<Color, String> map = new EnumMap<Color, String>(Color.class);
        map.put(Color.Blue, "Blue");
        map.put(Color.Yellow, "Yellow");
        map.put(Color.Red, "Red");
        System.out.println(map.get(Color.Red));
    }
}

使用枚舉類的注意事項

Java枚舉類知識詳細(xì)講解

枚舉類型對象之間的值比較,是可以使用==,直接來比較值,是否相等的,不是必須使用equals方法的喲。

因為枚舉類Enum已經(jīng)重寫了equals方法

/**
 * Returns true if the specified object is equal to this
 * enum constant.
 *
 * @param other the object to be compared for equality with this object.
 * @return  true if the specified object is equal to this
 *          enum constant.
 */
public final boolean equals(Object other) {
    return this==other;
}

枚舉類的實現(xiàn)原理

這部分參考 https://blog.csdn.net/mhmyqn/article/details/48087247

Java從JDK1.5開始支持枚舉,也就是說,Java一開始是不支持枚舉的,就像泛型一樣,都是JDK1.5才加入的新特性。通常一個特性如果在一開始沒有提供,在語言發(fā)展后期才添加,會遇到一個問題,就是向后兼容性的問題。

像Java在1.5中引入的很多特性,為了向后兼容,編譯器會幫我們寫的源代碼做很多事情,比如泛型為什么會擦除類型,為什么會生成橋接方法,foreach迭代,自動裝箱/拆箱等,這有個術(shù)語叫“語法糖”,而編譯器的特殊處理叫“解語法糖”。那么像枚舉也是在JDK1.5中才引入的,又是怎么實現(xiàn)的呢?

Java在1.5中添加了java.lang.Enum抽象類,它是所有枚舉類型基類。提供了一些基礎(chǔ)屬性和基礎(chǔ)方法。同時,對把枚舉用作Set和Map也提供了支持,即java.util.EnumSet和java.util.EnumMap。

接下來定義一個簡單的枚舉類

public enum Day {
    MONDAY {
        @Override
        void say() {
            System.out.println("MONDAY");
        }
    }
    , TUESDAY {
        @Override
        void say() {
            System.out.println("TUESDAY");
        }
    }, FRIDAY("work"){
        @Override
        void say() {
            System.out.println("FRIDAY");
        }
    }, SUNDAY("free"){
        @Override
        void say() {
            System.out.println("SUNDAY");
        }
    };
    String work;
    //沒有構(gòu)造參數(shù)時,每個實例可以看做常量。
    //使用構(gòu)造參數(shù)時,每個實例都會變得不一樣,可以看做不同的類型,所以編譯后會生成實例個數(shù)對應(yīng)的class。
    private Day(String work) {
        this.work = work;
    }
    private Day() {
    }
    //枚舉實例必須實現(xiàn)枚舉類中的抽象方法
    abstract void say ();
}

反編譯結(jié)果

D:\MyTech\out\production\MyTech\com\javase\枚舉類>javap Day.class
Compiled from "Day.java"
public abstract class com.javase.枚舉類.Day extends java.lang.Enum<com.javase.枚舉類.Day> {
  public static final com.javase.枚舉類.Day MONDAY;
  public static final com.javase.枚舉類.Day TUESDAY;
  public static final com.javase.枚舉類.Day FRIDAY;
  public static final com.javase.枚舉類.Day SUNDAY;
  java.lang.String work;
  public static com.javase.枚舉類.Day[] values();
  public static com.javase.枚舉類.Day valueOf(java.lang.String);
  abstract void say();
  com.javase.枚舉類.Day(java.lang.String, int, com.javase.枚舉類.Day$1);
  com.javase.枚舉類.Day(java.lang.String, int, java.lang.String, com.javase.枚舉類.Day$1);
  static {};
}

可以看到,一個枚舉在經(jīng)過編譯器編譯過后,變成了一個抽象類,它繼承了java.lang.Enum;而枚舉中定義的枚舉常量,變成了相應(yīng)的public static final屬性,而且其類型就抽象類的類型,名字就是枚舉常量的名字.

同時我們可以在Operator.class的相同路徑下看到四個內(nèi)部類的.class文件com/mikan/Day$1.class、com/mikan/Day$2.class、com/mikan/Day$3.class、com/mikan/Day$4.class,也就是說這四個命名字段分別使用了內(nèi)部類來實現(xiàn)的;同時添加了兩個方法values()和valueOf(String);我們定義的構(gòu)造方法本來只有一個參數(shù),但卻變成了三個參數(shù);同時還生成了一個靜態(tài)代碼塊。這些具體的內(nèi)容接下來仔細(xì)看看。

下面分析一下字節(jié)碼中的各部分,其中:

InnerClasses:
     static #23; //class com/javase/枚舉類/Day$4
     static #18; //class com/javase/枚舉類/Day$3
     static #14; //class com/javase/枚舉類/Day$2
     static #10; //class com/javase/枚舉類/Day$1

從中可以看到它有4個內(nèi)部類,這四個內(nèi)部類的詳細(xì)信息后面會分析。

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=5, locals=0, args_size=0
         0: new           #10                 // class com/javase/枚舉類/Day$1
         3: dup
         4: ldc           #11                 // String MONDAY
         6: iconst_0
         7: invokespecial #12                 // Method com/javase/枚舉類/Day$1."<init>":(Ljava/lang/String;I)V
        10: putstatic     #13                 // Field MONDAY:Lcom/javase/枚舉類/Day;
        13: new           #14                 // class com/javase/枚舉類/Day$2
        16: dup
        17: ldc           #15                 // String TUESDAY
        19: iconst_1
        20: invokespecial #16                 // Method com/javase/枚舉類/Day$2."<init>":(Ljava/lang/String;I)V
        //后面類似,這里省略
}

其實編譯器生成的這個靜態(tài)代碼塊做了如下工作:分別設(shè)置生成的四個公共靜態(tài)常量字段的值,同時編譯器還生成了一個靜態(tài)字段$VALUES,保存的是枚舉類型定義的所有枚舉常量
編譯器添加的values方法:

public static com.javase.Day[] values();  
  flags: ACC_PUBLIC, ACC_STATIC  
  Code:  
    stack=1, locals=0, args_size=0  
       0: getstatic     #2                  // Field $VALUES:[Lcom/javase/Day;  
       3: invokevirtual #3                  // Method "[Lcom/mikan/Day;".clone:()Ljava/lang/Object;  
       6: checkcast     #4                  // class "[Lcom/javase/Day;"  
       9: areturn  
這個方法是一個公共的靜態(tài)方法,所以我們可以直接調(diào)用該方法(Day.values()),返回這個枚舉值的數(shù)組,另外,這個方法的實現(xiàn)是,克隆在靜態(tài)代碼塊中初始化的$VALUES字段的值,并把類型強轉(zhuǎn)成Day[]類型返回。

造方法為什么增加了兩個參數(shù)?

有一個問題,構(gòu)造方法我們明明只定義了一個參數(shù),為什么生成的構(gòu)造方法是三個參數(shù)呢?

從Enum類中我們可以看到,為每個枚舉都定義了兩個屬性,name和ordinal,name表示我們定義的枚舉常量的名稱,如FRIDAY、TUESDAY,而ordinal是一個順序號,根據(jù)定義的順序分別賦予一個整形值,從0開始。在枚舉常量初始化時,會自動為初始化這兩個字段,設(shè)置相應(yīng)的值,所以才在構(gòu)造方法中添加了兩個參數(shù)。即:
另外三個枚舉常量生成的內(nèi)部類基本上差不多,這里就不重復(fù)說明了。

我們可以從Enum類的代碼中看到,定義的name和ordinal屬性都是final的,而且大部分方法也都是final的,特別是clone、readObject、writeObject這三個方法,這三個方法和枚舉通過靜態(tài)代碼塊來進行初始化一起。

它保證了枚舉類型的不可變性,不能通過克隆,不能通過序列化和反序列化來復(fù)制枚舉,這能保證一個枚舉常量只是一個實例,即是單例的,所以在effective java中推薦使用枚舉來實現(xiàn)單例。

枚舉類實戰(zhàn)

實戰(zhàn)一無參

(1)定義一個無參枚舉類

enum SeasonType {
    SPRING, SUMMER, AUTUMN, WINTER
}

(2)實戰(zhàn)中的使用

// 根據(jù)實際情況選擇下面的用法即可
SeasonType springType = SeasonType.SPRING;    // 輸出 SPRING 
String springString = SeasonType.SPRING.toString();    // 輸出 SPRING

實戰(zhàn)二有一參

(1)定義只有一個參數(shù)的枚舉類

enum SeasonType {
    // 通過構(gòu)造函數(shù)傳遞參數(shù)并創(chuàng)建實例
    SPRING("spring"),
    SUMMER("summer"),
    AUTUMN("autumn"),
    WINTER("winter");
    // 定義實例對應(yīng)的參數(shù)
    private String msg;
    // 必寫:通過此構(gòu)造器給枚舉值創(chuàng)建實例
    SeasonType(String msg) {
        this.msg = msg;
    }
    // 通過此方法可以獲取到對應(yīng)實例的參數(shù)值
    public String getMsg() {
        return msg;
    }
}

(2)實戰(zhàn)中的使用

// 當(dāng)我們?yōu)槟硞€實例類賦值的時候可使用如下方式
String msg = SeasonType.SPRING.getMsg();    // 輸出 spring

實戰(zhàn)三有兩參

(1)定義有兩個參數(shù)的枚舉類

public enum Season {
    // 通過構(gòu)造函數(shù)傳遞參數(shù)并創(chuàng)建實例
    SPRING(1, "spring"),
    SUMMER(2, "summer"),
    AUTUMN(3, "autumn"),
    WINTER(4, "winter");
    // 定義實例對應(yīng)的參數(shù)
    private Integer key;
    private String msg;
    // 必寫:通過此構(gòu)造器給枚舉值創(chuàng)建實例
    Season(Integer key, String msg) {
        this.key = key;
        this.msg = msg;
    }
    // 很多情況,我們可能從前端拿到的值是枚舉類的 key ,然后就可以通過以下靜態(tài)方法獲取到對應(yīng)枚舉值
    public static Season valueofKey(Integer key) {
        for (Season season : Season.values()) {
            if (season.key.equals(key)) {
                return season;
            }
        }
        throw new IllegalArgumentException("No element matches " + key);
    }
    // 通過此方法可以獲取到對應(yīng)實例的 key 值
    public Integer getKey() {
        return key;
    }
    // 通過此方法可以獲取到對應(yīng)實例的 msg 值
    public String getMsg() {
        return msg;
    }
}

(2)實戰(zhàn)中的使用

// 輸出 key 為 1 的枚舉值實例
Season season = Season.valueofKey(1);
// 輸出 SPRING 實例對應(yīng)的 key
Integer key = Season.SPRING.getKey();
// 輸出 SPRING 實例對應(yīng)的 msg
String msg = Season.SPRING.getMsg();

枚舉類總結(jié)

其實枚舉類懂了其概念后,枚舉就變得相當(dāng)簡單了,隨手就可以寫一個枚舉類出來。所以如上幾個實戰(zhàn)小例子一定要先搞清楚概念,然后在練習(xí)幾遍就 ok 了。

重要的概念,我在這里在贅述一遍,幫助老鐵們快速掌握這塊知識,首先記住,枚舉類中的枚舉值可以沒有參數(shù),也可以有多個參數(shù),每一個枚舉值都是一個實例;

并且還有一點很重要,就是如果枚舉值有 n 個參數(shù),那么構(gòu)造函數(shù)中的參數(shù)值肯定有 n 個,因為聲明的每一個枚舉值都會調(diào)用構(gòu)造函數(shù)去創(chuàng)建實例,所以參數(shù)一定是一一對應(yīng)的;既然明白了這一點,那么我們只需要在枚舉類中把這 n 個參數(shù)定義為 n 個成員變量,然后提供對應(yīng)的 get() 方法,之后通過實例就可以隨意的獲取實例中的任意參數(shù)值了。

如果想讓枚舉類更加的好用,就可以模仿我在實戰(zhàn)三中的寫法那樣,通過某一個參數(shù)值,比如 key 參數(shù)值,就能獲取到其對應(yīng)的枚舉值,然后想要什么值,就 get 什么值就好了。

枚舉 API

我們使用 enum 定義的枚舉類都是繼承 java.lang.Enum 類的,那么就會繼承其 API ,常用的 API 如下:

  • String name()

獲取枚舉名稱

  • int ordinal()

獲取枚舉的位置(下標(biāo),初始值為 0 )

  • valueof(String msg)

通過 msg 獲取其對應(yīng)的枚舉類型。(比如實戰(zhàn)二中的枚舉類或其它枚舉類都行,只要使用得當(dāng)都可以使用此方法)

  • values()

獲取枚舉類中的所有枚舉值(比如在實戰(zhàn)三中就使用到了)

總結(jié)

枚舉本質(zhì)上是通過普通的類來實現(xiàn)的,只是編譯器為我們進行了處理。每個枚舉類型都繼承自java.lang.Enum,并自動添加了values和valueOf方法。

而每個枚舉常量是一個靜態(tài)常量字段,使用內(nèi)部類實現(xiàn),該內(nèi)部類繼承了枚舉類。所有枚舉常量都通過靜態(tài)代碼塊來進行初始化,即在類加載期間就初始化。

另外通過把clone、readObject、writeObject這三個方法定義為final的,同時實現(xiàn)是拋出相應(yīng)的異常。這樣保證了每個枚舉類型及枚舉常量都是不可變的。可以利用枚舉的這兩個特性來實現(xiàn)線程安全的單例。

到此,相信大家對“Java枚舉類知識詳細(xì)講解”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI