
用 int 常量的零對象開銷換掉 enum 的貴——并在編譯期保留一層類型安全檢查。本文梳理 IntDef 的核心價值、Java/Kotlin 雙版本用法、一個可運行 Demo以及幾個容易踩的坑。本篇為獨立 Android 開發(fā) Tips與「十年車機性能優(yōu)化系列」并列可單獨閱讀。一、為什么要在 Android 上慎用 enum在 Android 里每個enum常量都是一個對象實例它不僅占堆內(nèi)存還會在 DEX 里生成額外的類結構、values()/valueOf()等方法直接推高包體積。舉個例子一個只有 3 個值的enumpublic enum WeekDay { SUNDAY, MONDAY, TUESDAY }編譯后等價于一個繼承了java.lang.Enum的類附帶靜態(tài)初始化塊、數(shù)組和若干方法。在服務器/桌面端這微不足道但在車載座艙、低端機這種內(nèi)存和存儲都緊張的場景大量使用 enum 會積少成多。也正因如此Android 官方的 API 設計指南明確要求在平臺 API 中應優(yōu)先使用IntDef而非enum。一句話enum提供強類型但貴IntDef幾乎免費只犧牲一點類型強度。二、IntDef 是什么 核心優(yōu)勢IntDef來自androidx.annotation它不改變底層類型——變量本質(zhì)還是int基本類型只是用注解把允許取哪些值標注出來讓 Android Studio / Lint 在編譯期給你標黃警告。特性IntDefstatic final intenum內(nèi)存占用小本質(zhì)是int常量大每個常量都是對象有額外內(nèi)存開銷DEX 文件大小小僅增加常量定義大會生成Enum相關的方法和對象類型安全編譯時 IDE 警告非強約束強類型編譯時直接報錯完全安全代碼可讀性中等需查看注解定義高枚舉自帶清晰的命名空間和語義功能擴展弱僅能表示整數(shù)值極強可實現(xiàn)接口、包含方法、定義屬性等三、Java 版完整用法三步1. 定義常量 創(chuàng)建注解import androidx.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public class WeekDay { // 1. 定義常量編譯期常量 public static final int SUNDAY 0; public static final int MONDAY 1; public static final int TUESDAY 2; // 2. 用 IntDef 把常量集合起來并聲明為 SOURCE 保留 IntDef({SUNDAY, MONDAY, TUESDAY}) Retention(RetentionPolicy.SOURCE) // 僅源碼期保留不進字節(jié)碼 public interface WeekDays { } }2. 用注解限制字段 / 參數(shù) / 返回值public class Demo { // 限制字段 WeekDays private int today WeekDay.SUNDAY; // 限制方法參數(shù) public void setToday(WeekDays int day) { this.today day; } // 限制返回值 WeekDays public int getToday() { return today; } }這樣當你傳入WeekDay.WEDNESDAY未定義常量或隨便寫一個數(shù)字setToday(99)Android Studio 會標黃警告實現(xiàn)編譯期類型安全檢查。定義 static final int 常量用 IntDef 包成注解標注到 參數(shù)/字段/返回值IDE/Lint 編譯期標黃警告四、Kotlin 版完整用法Kotlin 里常量要用const val編譯期常量否則無法寫進IntDef的 value。import androidx.annotation.IntDef import androidx.annotation.Retention import androidx.annotation.RetentionPolicy object WeekDay { const val SUNDAY 0 const val MONDAY 1 const val TUESDAY 2 } Retention(RetentionPolicy.SOURCE) IntDef(WeekDay.SUNDAY, WeekDay.MONDAY, WeekDay.TUESDAY) annotation class WeekDays class Demo { WeekDays private var today: Int WeekDay.SUNDAY fun setToday(WeekDays day: Int) { this.today day } WeekDays fun getToday(): Int today }Kotlin 也有自己的enum class用法和 Java enum 一樣貴。若只是為了限定取值集合Kotlin IntDef同樣適用。五、進階flag true 支持位運算當常量需要組合使用如FLAG_READ | FLAG_WRITE開啟flag trueIntDef(flag true, value {FLAG_READ, FLAG_WRITE, FLAG_EXECUTE}) Retention(RetentionPolicy.SOURCE) public interface FlagMode {} // 使用 FlagMode private int mode FLAG_READ | FLAG_WRITE; public boolean hasFlag(FlagMode int mode, int flag) { return (mode flag) flag; // 用位與判斷是否包含某標志 }開啟flag true后Lint 允許這些位任意組合的值通過只要每一位都來自定義集合即可。六、完整可運行 Demo下面是一份純 Java、可直接javac運行的 Demo。為保證脫離 Android 也能跑我們把IntDef在本地定義了一個同形狀注解生產(chǎn)環(huán)境直接換成androidx.annotation.IntDef用法完全一致。import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; // —— 本地版 IntDef形狀與 androidx.annotation.IntDef 一致僅用于無 Android 依賴演示—— Retention(RetentionPolicy.SOURCE) Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) interface IntDef { int[] value() default {}; boolean flag() default false; } public class IntDefDemo { // 1. 常量 public static final int SUNDAY 0; public static final int MONDAY 1; public static final int TUESDAY 2; // 2. 注解 IntDef({SUNDAY, MONDAY, TUESDAY}) Retention(RetentionPolicy.SOURCE) interface WeekDays {} // 3. 使用 WeekDays private int today SUNDAY; public void setToday(WeekDays int day) { this.today day; } WeekDays public int getToday() { return today; } public static void main(String[] args) { IntDefDemo d new IntDefDemo(); d.setToday(IntDefDemo.MONDAY); System.out.println(今天是 d.getToday()); // 輸出 1 // 下面這行若取消注釋Android Studio / Lint 會標黃警告編譯仍能通過 // d.setToday(99); // ? 不在 {0,1,2} 集合內(nèi) } }編譯運行javac IntDefDemo.java java IntDefDemo # 輸出今天是1在真實 Android 項目中把IntDefDemo里的本地IntDef刪掉改為import androidx.annotation.IntDef;即可——其余代碼一行都不用改。七、踩坑注意SOURCE 保留運行時拿不到Retention(SOURCE)意味著注解在編譯后就被丟棄不能靠反射讀取它做運行時校驗。這既是省開銷的優(yōu)點也是無法運行時兜底的限制。IDE 警告 ≠ 編譯報錯IntDef是 Lint/IDE 的軟約束編譯能通過。如果你的 CI 不跑lint錯誤的int值照樣進包。要強制攔截需在 CI 跑./gradlew lint或?qū)懽远x Lint 規(guī)則。Kotlin 必須用const val寫在companion object里的普通val不是編譯期常量塞不進IntDef({...})會直接編譯報錯。只有頂層的const val或object內(nèi)的const val才行。漏寫常量 漏檢IntDef的 value 數(shù)組里若漏了某個常量那個常量就不在受檢集合內(nèi)調(diào)用方傳它不會報警告。增刪常量時務必同步更新 value。flag 組合仍可能越界flag true只保證每一位都來自集合不保證組合后的值合法。若某些組合業(yè)務上不允許需要自己在代碼里校驗。別為了像 enum 而過度設計IntDef只能表達整數(shù)值無法攜帶數(shù)據(jù)或行為。當常量需要關聯(lián)顏色、文案或要實現(xiàn)抽象方法時enum才是更合適的選擇——性能不是唯一維度。小結IntDef是 Android 上用最小代價換類型安全的標準做法底層是int、零對象開銷、不增大 DEX還能在寫代碼時攔下非法取值。絕大多數(shù)常量集合場景優(yōu)先用它只有當常量需要攜帶數(shù)據(jù)或有行為邏輯時才值得為enum的貴買單。