1. 程式人生 > >Android進階之將註解@IntDef @StringDef替代列舉類(enum)

Android進階之將註解@IntDef @StringDef替代列舉類(enum)

1 概述

Enum是java中一種包含固定常數的型別。當我們需要預先定義一些值,並限定範圍時,使用 Enum,來做到編寫和編譯都查錯。
  Java的Enum的實質是特殊單例的靜態成員變數,可以在編寫器,編譯器做到各種靜態檢查防呆;在執行期,所有列舉類作為單例,全部載入到記憶體中。
  因此,Enum增加了APK的記憶體佔用,比常量多5到10倍的記憶體佔用。所以放棄列舉,就是關於安卓應用效能的記憶體佔用部分的最佳實踐方法之一。

2 為什麼要使用列舉

2.1 例子

public class SexTest {
    private final int MAN = 101;
    private final int WOMEN = 102;
    private int sex;

    //設定性別
    private void setSex(int sex) {
        this.sex = sex;
    }

    // 獲取性別
    private String getSex() {
        if (MAN == sex) {
            return "男";
        }  else if (WOMEN == sex) {
            return "女";
        }  else {
            return "未知";
        }
    }

    public void main() {
        // 設定為101入參,而非限定的MAN
        setSex(101);
        String sex = getSex();
        System.out.println("sex: " + sex); // 輸出:sex: 男

        // 設定為102入參
        setSex(102);
        String resultSex = getSex();
        System.out.println("resultSex: " + resultSex);  // 輸出:resultSex: 未知
    }
}

2.2 缺點

當我們定義了一個男女的final整型作為入參時,不一定保證入參的都是我們想要的入參,這裡就有一個 型別不安全的問題出現,而列舉就可以解決這個問題。

3 列舉類

3.1 例子

public class SexEnumTest {
    public enum Sex {
        MAN, WOMEN
    }

    private Sex sex;

    // 設定性別
    private void setSex(Sex sex) {
        this.sex = sex;
    }

    // 獲取性別
    private String getSex() {
        if (MAN == sex) {
            return "男";
        }  else if (WOMEN == sex) {
            return "女";
        }  else {
            return "未知";
        }
    }

    public void main() {
        // 這裡的入參必須為Sex列舉類中的其中一個列舉常量
        // 絕對不允許輸入沒有再Sex列舉裡面定義的常量
        setSex(Sex.MAN);
        // 錯誤
        setSex(101);

        String resultSex = getSex();
        System.out.println("resultSex: " + resultSex);  //out:resultSex: 男
    }
}

3.2 優點

(1)利用列舉,在setSex()方法裡面對入參做了列舉Sex的限制,如圖:
在這裡插入圖片描述
(2)對於想輸入任何非列舉類Sex裡面定義的列舉常量,編譯都是不能通過的;
(3)這就很好的限制了入參混亂的問題。

3.3 缺點

(1)每一個列舉值都是一個單例物件,在使用它時會增加額外的記憶體消耗,所以列舉相比與Integer和String會佔用更多的記憶體;
(2)較多的使用Enum會增加DEX檔案的大小,會造成執行時更多的IO開銷,使我們的應用需要更多的空間。特別是分dex多的大型APP,列舉的初始化很容易導致ANR。

4 不使用列舉型別,使用@IntDef/@StringDef + @interface的解決方案

既然是因為引數的型別太泛了造成的型別不安全,那麼我只要將引數限定在某一個型別集合裡面,用@IntDef/@StringDef + @interface來進行限定引數。

4.1 例子

public class SexIntDef {
    // 表示開啟Doc文件
    @Documented 
    @IntDef({SEX.UNKNOWN, SEX.MALE, SEX.FEMALE})
    // 表示註解作用範圍,引數註解,成員註解,方法註解
    @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD}) 
    // 表示註解屬於(編譯)級別,僅存在於原始碼中,在class位元組碼檔案中不包含。
    @Retention(RetentionPolicy.SOURCE) 
    // 介面,定義新的註解型別
    public @interface SEX {   
        int UNKNOWN = 0; // 未知(保密)
        int MALE = 1;    // 男
        int FEMALE = 2;  // 女
    }

    private @SEX int sex = SEX.MALE;

    private @SEX int sex1 = 101;  // 錯誤

    private void setSex(@SEX int sex){
        this.sex = sex;
    }

    public void main() {
        // 絕對不允許輸入沒有在@IntDef/@StringDef + @interface限定的引數
        setSex(SEX.MALE);
        // 錯誤
        setSex(1);
    }
}

4.2 優點

(1)如果我們嘗試在呼叫setSex()方法的時候,傳入不在限定之內的值,那麼編譯就不會通過,有錯誤提示,如圖:
在這裡插入圖片描述

4.3 同理,我們也可以使用@StringDef

4.4 常用模式

    // 性別
    @IntDef({SEX.UNKNOWN, SEX.MALE, SEX.FEMALE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SEX {
        int UNKNOWN = 0;    // 未知(保密)
        int MALE = 1;       // 男
        int FEMALE = 2;     // 女
    }

5 學習連結

Android中不使用列舉類(enum)替代為@IntDef @StringDef