深入理解Java枚舉類型 + EnumMap源碼分析

一、基本概念

枚舉是Java1.5引入的新特性,通過關鍵字enum來定義枚舉類。枚舉類是一種特殊類,它和普通類一樣可以使用構造器、定義成員變量和方法,也能實現一個或多個接口,但枚舉類不能繼承其他類。

二、枚舉的優缺點

1、優點

Effctive Java中之所以推薦用枚舉代替所有常量Code,原因如下:

(1)類型檢查,有效性檢查

(2)枚舉作爲一個類,可以有自己的屬性(通常應該是常量,我沒遇到過不是的情況)以及自己的方法(否則只能用switch來寫,實際違反原則)

(3)和常量相比,無需查看文檔和源碼就能直接知道所有可能返回值,方便編碼。

然而這裏的問題就出在第一點上,實際上分佈式環境下(1)並不是必然的。如果業務處理中允許某個接口返回值有未定義內容,那麼在反序列化中就不該對此拋出異常,不必死守(1)。同時,從第(2)點和第(3)點來看,這樣限制枚舉的使用範圍造成的影響是極大的。將有自己屬性,自己方法實現的枚舉改寫爲code和其他方法的配合,需要的代碼量上升不少,而且代碼腐爛度極大增加。

2、缺點

(1)由於Java中支持單繼承,因此枚舉類型不能再繼承其他類;

(2)使用枚舉作爲返回值可能造成的問題其實大家都知道就是客戶端和服務端版本不一致的話,會造成反序列化異常,於是《阿里巴巴JAVA開發手冊》對於這個問題的處理辦法就採取了儘量避免異常出現,所以禁止定義枚舉爲返回值。

三、解決ifelse

對於業務開發來說,業務邏輯的複雜是必然的,隨着業務發展,需求只會越來越複雜,爲了考慮到各種各樣的情況,代碼中不可避免的會出現很多if-else。

一旦代碼中if-else過多,就會大大的影響其可讀性和可維護性,而且代碼顯得很low。

枚舉可以解決這個問題;

關於枚舉與switch是個比較簡單的話題,使用switch進行條件判斷時,條件參數一般只能是整型,字符型。而枚舉型確實也被switch所支持,在java 1.7後switch也對字符串進行了支持。這裏我們簡單看一下switch與枚舉類型的使用

​static void testSwitch(Week week){
    switch (week){
        case MONDAY:
            System.out.println(week.getMeaning());
            break;
        case TUESDAY:
            System.out.println(week.getMeaning());
            break;
        case WEDNESDAY:
            System.out.println(week.getMeaning());
            break;
        case THURSDAY:
            System.out.println(week.getMeaning());
            break;
        case FRIDAY:
            System.out.println(week.getMeaning());
            break;
        case SATURDAY:
            System.out.println(week.getMeaning());
            break;
        case SUNDAY:
            System.out.println(week.getMeaning());
            break;
        default:
            System.out.println("您輸入有誤");
            break;
    }
}

​

四、枚舉的常用方法

 1、枚舉類

enum Weekday {
    SUNDAY(0),
    MONDAY(1),
    TUESDAY(2),
    WEDNESDAY(3),
    THURSDAY(4),
    FRIDAY(5),
    SATURDAY(6);
    
    private int value;
    
    Weekday(int value) {
        this.value = value;
    }
}

2、枚舉類變量中添加屬性

enum Week{
    SUNDAY(0,"星期日"),
    MONDAY(1,"星期一"),
    TUESDAY(2,"星期二"),
    WEDNESDAY(3,"星期三"),
    THURSDAY(4,"星期四"),
    FRIDAY(5,"星期五"),
    SATURDAY(6,"星期六");

    private int id;
    private String meaning;

    Week(int id,String meaning) {
        this.id = id;
        this.meaning = meaning;
    }
}

3、測試類 

public static void main(String[] args) {
        //1、ordinal(),枚舉順序值
        System.out.println("枚舉順序值,"+Weekday.MONDAY.ordinal());//1

        /*
         * 2、valueOf()
         * public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
         * enumType -- 這是枚舉類型,返回一個常量的類的對象。
         * name -- 這是常量,要返回的名稱。
         * return:此方法返回具有指定名稱的枚舉類型的枚舉常量。
         * 如果你傳了一個不存在的字符串,那麼會拋出異常。
         * */
        Week week = Enum.valueOf(Week.class,Week.MONDAY.name().toString());
        Week week1 = Week.valueOf(Week.class,Week.MONDAY.name());
        System.out.println("Enum.valueOf,"+week);//MONDAY
        System.out.println("Week.valueOf,"+week1);//MONDAY

        //3、values()
        System.out.println("Week.values(),"+Weekday.values());//返回一個Weekday數組,[Ljavase.enumeration.Weekday;@2a84aee7

        //4、通過compareTo方法比較,實際上其內部是通過ordinal()值比較的
        System.out.println("Weekday.MONDAY.compareTo(Weekday.TUESDAY),"+Weekday.MONDAY.compareTo(Weekday.TUESDAY));//false

        //5、獲取該枚舉對象的Class對象引用,當然也可以通過getClass方法
        Class<?> declaringClass = Weekday.MONDAY.getDeclaringClass();
        System.out.println("獲取該枚舉對象的Class對象引用,"+declaringClass);//javase.enumeration.Weekday

        //6、通過getEnumConstants()獲取該枚舉類型的所有元素,如果Class對象不是枚舉類型,則返回null。
        Object[] enumConstants = declaringClass.getEnumConstants();
        //枚舉類個數,Weekday.values().length)
        for (int i = 0; i < Weekday.values().length; i++) {
            System.out.println("getEnumConstants,"+enumConstants[i]);//SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY
        }

        //7、判斷是否是枚舉類型
        System.out.println("declaringClass.isEnum(),"+declaringClass.isEnum());//true

        //8、獲取枚舉變量的屬性
        System.out.println("編號,"+Week.MONDAY.getId()+",含義,"+Week.MONDAY.getMeaning());
    }

4、向上轉型

//9、向上轉型Enum
Enum e = Weekday.FRIDAY;

 

這個東西意義何在,有待研究。

5、部分方法源碼介紹

(1)valueof

public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}

(2)compareTo 

public final int compareTo(E o) {
    Enum<?> other = (Enum<?>)o;
    Enum<E> self = this;
    if (self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass())
        throw new ClassCastException();
    return self.ordinal - other.ordinal;
}

五、枚舉類中定義抽象方法

1、定義抽象方法

package javase.enumeration;

public enum Season{
    SPRING("1"){
        @Override
        public Season getNextSeason() {
            return SUMMER;
        }
    },
    SUMMER("4"){
        @Override
        public Season getNextSeason() {
            return AUTUMN;
        }
    },
    AUTUMN("7"){
        @Override
        public Season getNextSeason() {
            return WINTER;
        }
    },
    WINTER("10"){
        @Override
        public Season getNextSeason() {
            return SPRING;
        }
    };

    private String monthStart;
    private Season(String monthStart){
        this.monthStart = monthStart;
    }

    public abstract Season getNextSeason();
}

2、測試類

class Test{
    public static void main(String[] args) {
        System.out.println("SPRING下一個季節是:"+Season.SPRING.getNextSeason());
    }
}

3、控制檯輸出

六、實現接口 

1、接口

package javase.enumeration;

public interface Study {
    void StudyJava();
    void StudyDataStrucure();
    void StudyDesignMode();
    void StudySpring();
}

2、實現接口 

enum Week implements Study{
    SUNDAY(0,"星期日"),
    MONDAY(1,"星期一"),
    TUESDAY(2,"星期二"),
    WEDNESDAY(3,"星期三"),
    THURSDAY(4,"星期四"),
    FRIDAY(5,"星期五"),
    SATURDAY(6,"星期六");

    private int id;
    private String meaning;

    Week(int id,String meaning) {
        this.id = id;
        this.meaning = meaning;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getMeaning() {
        return meaning;
    }

    public void setMeaning(String meaning) {
        this.meaning = meaning;
    }

    @Override
    public String toString() {
        return "Week{" +
                "id=" + id +
                ", meaning='" + meaning + '\'' +
                '}';
    }

    @Override
    public void StudyJava() {
        System.out.println(this+"學習java");
    }

    @Override
    public void StudyDataStrucure() {
        System.out.println(this.meaning+"學習數據結構");
    }

    @Override
    public void StudyDesignMode() {
        System.out.println(this.meaning+"學習設計模式");
    }

    @Override
    public void StudySpring() {
        System.out.println(this.meaning+"學習Spring框架");
    }
}

3、測試類

//10、實現接口並調用接口中方法
MONDAY.StudyDataStrucure();

4、控制檯輸出

七、枚舉實現單例

枚舉單例(Enum Singleton)在Effective Java一書中提到,因爲其功能完善,使用簡潔,無償的提供了序列化機制,在面對複雜的序列化或者反射攻擊時依然可以絕對防止多次實例化等優點,被作者所推崇。

public enum  DataSourceEnum {
    DATASOURCE;
    private DBConnection connection = null;
    private DataSourceEnum(){
        connection = new DBConnection();
    }
    public DBConnection getConnection(){
        return connection;
    }
}

八、EnumMap

 1、代碼實例分析EnumMap和HashMap

以1月到6月學習不同的Java項目爲例。

/*
* 以1月到6月學習不同的Java項目爲例
* */
public class EnumMapDemo {
    public static void main(String[] args) {
        List<StudyJava> list = new ArrayList<>();
        list.add(new StudyJava("January",ProjectEnum.SSH));
        list.add(new StudyJava("February",ProjectEnum.SSM));
        list.add(new StudyJava("March",ProjectEnum.SSM));
        list.add(new StudyJava("April",ProjectEnum.SPRINGBOOT));
        list.add(new StudyJava("May",ProjectEnum.SPRINGBOOT));
        list.add(new StudyJava("June",ProjectEnum.SPRINGCLOUD));
        //1、使用hashmap
        Map<ProjectEnum,Integer> hashMap = new HashMap<>();
        for (StudyJava studyJava:list){
            ProjectEnum project = studyJava.getProjectEnum();
            Integer count = hashMap.get(project);
            if(count != null){
                hashMap.put(project,count + 1);
            }else{
                hashMap.put(project,1);
            }
        }
        System.out.println(hashMap.toString());

        //2、使用EnumMap
        Map<ProjectEnum,Integer> enumMap = new EnumMap(ProjectEnum.class);
        for (StudyJava studyJava:list){
            ProjectEnum project = studyJava.getProjectEnum();
            Integer count = enumMap.get(project);
            if(count!=null){
                enumMap.put(project,count+1);
            }else {
                enumMap.put(project,1);
            }
        }
        System.out.println(enumMap.toString());
    }
}

enum ProjectEnum {
    SSM,
    SSH,
    SPRINGBOOT,
    SPRINGCLOUD;
}

class StudyJava{
    private String month;
    private ProjectEnum projectEnum;

    public StudyJava(String month, ProjectEnum projectEnum) {
        this.month = month;
        this.projectEnum = projectEnum;
    }

    public String getMonth() {
        return month;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public ProjectEnum getProjectEnum() {
        return projectEnum;
    }

    public void setProjectEnum(ProjectEnum projectEnum) {
        this.projectEnum = projectEnum;
    }
}

2、控制檯輸出

3、結果分析

HashMap對比EnumMap

HashMap和EnumMap的輸出結果一致,證明枚舉類型都可以使用,但是EnumMap作爲枚舉的專屬的集合,我們沒有理由再去使用HashMap,畢竟EnumMap要求其Key必須爲Enum類型。

由於枚舉類型實例的數量相對固定並且有限,所以EnumMap使用數組來存放與枚舉類型對應的值,畢竟數組是一段連續的內存空間,根據程序局部性原理,效率會相當高。

  • EnumMap需要傳遞一個類型信息,即Class對象,通過這個參數EnumMap就可以根據類型信息初始化器內部數據結構,也可以初始化時傳入一個Map集合。
  • key值不能爲null。

4、簡單的源碼分析

(1)EnumMap繼承了AbstractMap類,因此EnumMap具備一般map的使用方法。

(2)由於key值基本固定,底層數組實現,效率更高

(3)獲取key數組方法

(4)put()

(5)get()

(6)remove()

(7)containsKey()

以上就是EnumMap的源碼分析,即內部有兩個數組,長度相同,一個是以枚舉爲key的數組,一個是對應的值數組,key不允許null,value可以爲null,鍵都有一個對應的索引,根據索引直接訪問和操作其鍵數組和值數組,由於操作時都是數組,所以效率比HashMap高。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章