1. 程式人生 > 實用技巧 >21-列舉與註解

21-列舉與註解

列舉型別

  • 有時候,變數(物件) 的取值只在一個有限的集合內。比如:
    • 星期:Monday(星期一)、...、Sunday(星期天)
    • 性別:Man(男)、Woman(女)
    • 季節:Spring(春節)、Summer(夏天),Autumn(秋天),Winter(冬天)
    • 支付方式:Cash(現金)、WeChatPay(微信)、Alipay(支付寶)、BankCard(銀行卡)、CreditCard(信用卡)
    • 就職狀態:Busy、Free、Vocation、Dimission
    • 訂單狀態:Nonpayment(未付款)、Paid(已付款)、Delivered(已發貨)、 Return(退貨)、Checked(已確認)、Fulfilled(已配貨)
    • 執行緒狀態:建立、就緒、執行、阻塞、死亡
  • 針對這種情況,可以自定義列舉型別。列舉型別包括有限個的物件。
    • JDK5.0 之前需要自定義列舉類
    • JDK5.0 新增的 enum 用於定義列舉類
  • 若列舉只有一個物件, 則可以作為一種 [單例模式] 的實現方式
  • 當需要定義一組常量時,強烈建議使用列舉類

定義列舉類

自定義列舉類

  • 列舉類物件的屬性不應允許被改動,所以應該使用 private final 修飾
  • private final 修飾的屬性應該在 [構造器] 中為其賦值
  • 若列舉類顯式的定義了帶引數的構造器,則在列出列舉值時也必須對應的傳入引數
  • 私有化類的構造器,保證不能在類的外部建立其物件
  • 在類的內部建立列舉類的例項。宣告為:public static final
// JDK5.0 之前,自定義列舉類
class Season {
    // 1. 宣告 Season 物件的屬性(private final)
    private final String seasonName;
    private final String seasonDesc;

    // 2. 私有化類的構造器
    private Season(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    // 3. 提供當前列舉類的多個物件
    public static final Season SPRING = new Season("春天", "春暖花開");
    public static final Season SUMMER = new Season("夏天", "夏日炎炎");
    public static final Season AUTUMN = new Season("秋天", "秋高氣爽");
    public static final Season WINTER = new Season("冬天", "冰天雪地");

    // 4. [訴求1] 獲取列舉類物件的屬性
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    // 5. [訴求2] 提供 toString()
    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }
}

使用 enum 定義列舉類

  • 使用 enum 定義的列舉類預設繼承了 java.lang.Enum 類,因此不能再繼承其他類
  • 列舉類的構造器只能使用 private 許可權修飾符
  • 列舉類的所有例項必須在列舉類中顯式列出(',' 分隔 & ';' 結尾) 列出的
  • 例項系統會自動新增 public static final 修飾 // 所以你不要多此一舉
  • 必須在列舉類的第一行宣告列舉類物件
// JDK5.0 之後,使用 enum 關鍵字定義列舉類 (預設繼承於 Java.lang.Enum)
enum Season1 { // extends Enum<Season1>
    // 1. 提供當前列舉類的物件,物件之間用 "," 隔開
    SPRING("春天", "春暖花開"), SUMMER("夏天", "夏日炎炎"),
    AUTUMN("秋天", "秋高氣爽"), WINTER("冬天", "冰天雪地");

    // 2. 宣告 Season 物件的屬性(private final)
    private final String seasonName;
    private final String seasonDesc;

    // 3. 私有化類的構造器
    private Season1(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    // 4. [訴求] 獲取列舉類物件的屬性
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    // toString(): 列印引用名
}

JDK5.0 中可以在 switch 表示式中使用 Enum 定義的列舉類的物件作為表示式,case 子句可以直接使用列舉值的名字,無需新增列舉類作為限定。

Enum類

  • 使用 enum 定義的列舉類預設繼承了 java.lang.Enum
  • Enum 類的主要方法
    • values():返回列舉型別的物件陣列。該方法可以很方便地遍歷所有的列舉值
    • valueOf(String str):可以把一個字串轉為對應的列舉類物件。要求字串必須是列舉類物件常量名。如不是,會丟擲執行時異常:IllegalArgumentException
    • toString():返回當前列舉類物件常量的名稱

實現介面的列舉類

  1. 實現介面,在 enum 類中實現抽象方法 // 每個物件呼叫此方法,執行相同的方法體
  2. 讓 enum 類中的物件分別實現介面中的抽象方法
interface Info {
    void func();
}

enum Season1 implements Info { // extends Enum<Season1>

    SPRING("春天", "春暖花開") {
        @Override
        public void func() {
            System.out.println("春天的味道");
        }
    },
    SUMMER("夏天", "夏日炎炎") {
        @Override
        public void func() {
            System.out.println("夏天的味道");
        }
    },
    AUTUMN("秋天", "秋高氣爽") {
        @Override
        public void func() {
            System.out.println("秋天的味道");
        }
    },
    WINTER("冬天", "冰天雪地") {
        @Override
        public void func() {
            System.out.println("冬天的味道");
        }
    };
    
    /*
    @Override
    public void func() {
        System.out.println("季節是有氣味的");
    }
    希望每個物件呼叫func(),展現出不同的內容 → 每個物件各自實現func()
    */
    
    // ...
}

註解

概述

  • 從 JDK 5.0 開始, Java 增加了對 元資料(MetaData) 的支援, 也就是 Annotation(註解)
  • Annotation 其實就是程式碼裡的特殊標記,這些標記可以在編譯、類載入、執行時被讀取,並執行相應的處理。通過使用 Annotation,程式設計師可以在不改變原有邏輯的情況下,在原始檔中嵌入一些補充資訊。程式碼分析工具、開發工具和部署工具可以通過這些補充資訊進行驗證或者進行部署
  • Annotation 可以像修飾符一樣被使用,可用於修飾包、類、構造器、方法、成員變數、引數、區域性變數的宣告,這些資訊被儲存在 Annotation 的 "name=value" 對中
  • 在 JavaSE中,註解的使用目的比較簡單,例如標記過時的功能, 忽略警告等。在 JavaEE/Android 中註解佔據了更重要的角色,例如 用來配置應用程式的任何切面,代替 JavaEE 舊版中所遺留的繁冗 程式碼和XML配置等
  • 未來的開發模式都是基於註解的,JPA 是基於註解的,Spring2.5 以上都是基於註解的,Hibernate3.x 以後也是基於註解的,現在的 Struts2 有一部分也是基於註解的了。註解是一種趨勢。一定程度上可以說:框架 = 註解 + 反射 + 設計模式

常見的Annotation示例

使用 Annotation 時要在其前面增加 @ 符號, 並把該 Annotation 當成 一個修飾符使用,用於修飾它支援的程式元素。

  • [示例1] 生成文件相關的註解
  • [示例2] 在編譯時進行格式檢查(JDK內建的三個基本註解)
    • @Override 限定重寫父類方法, 該註解只能用於方法
    • @Deprecated 用於表示所修飾的元素(類, 方法等)已過時。通常是因為所修飾的結構危險或存在更好的選擇
    • @SuppressWarnings 抑制編譯器警告
  • [示例3] 跟蹤程式碼依賴性,實現替代配置檔案功能
    • Servlet3.0 提供了註解(annotation),使得不再需要在 web.xml 檔案中進行 Servlet 的部署
    • spring 框架中關於“事務”的管理

自定義 Annotation

自定義註解必須配上註解的資訊處理流程(反射)才有意義

  • 定義新的 Annotation 型別使用 @interface 關鍵字
  • 自定義註解自動繼承了 java.lang.annotation.Annotation<I>
  • Annotation 的成員變數 在 Annotation 定義中以無引數方法的形式來宣告,其方法名和返回值定義了該成員的名字和型別,我們稱為 [配置引數]。型別只能是 8 種基本資料型別、String型別、Class型別、enum型別、Annotation型別及以上所有型別的陣列
  • 可以在定義 Annotation 的成員變數時為其指定初始值, 指定成員變數的初始值可使用 default 關鍵字
  • 如果只有一個引數成員,建議使用引數名為 "value"
  • 如果定義的註解含有配置引數,那麼使用時必須指定引數值,除非它有預設值。格式是 "引數名 = 引數值",如果只有一個引數成員,且名稱為 value, 可以省略 "value="
  • 沒有成員定義的 Annotation 稱為 [標記];包含成員變數的 Annotation 稱為 [元資料Annotation]
public @interface MyAnnotation {
    String value() default "Hello";
}

元註解

JDK 的元Annotation 用於修飾其他 Annotation 定義。JDK5.0提供了4個標準的 meta-annotation 型別,分別是:RetentionTargetDocumentedInherited

  • @Retention
    • 只能用於修飾一個 Annotation 定義,用於指定該 Annotation 的生命週期,@Rentention 包含一個 RetentionPolicy 列舉型別的成員變數
    • 使用 @Rentention 時必須為該名為 value 成員變數指定值
      • RetentionPolicy.SOURCE:在原始檔中有效(即原始檔保留),編譯器直接丟棄這種策略的註釋
      • RetentionPolicy.CLASS:在 class 檔案中有效(即class保留),當執行 Java 程式時,JVM 不會保留註解。 這是預設值
      • RetentionPolicy.RUNTIME:在執行時有效(即執行時保留),執行 Java 程式時, JVM 會 保留註釋。程式可以通過反射獲取該註釋
    • 圖示
  • @Target
    • 用於修飾 Annotation 定義,用於指定被修飾的 Annotation 能用於修飾哪些程式元素。@Target 包含一個 ElementType 列舉型別的成員變數
    • 使用 @Rentention 時必須為該名為 value 成員變數指定值
  • @Documented
    • 用於指定被該 元Annotation 修飾的 Annotation 類將被 javadoc 工具提取成文件
    • 預設情況下,javadoc 是不包括註解的
    • 定義為 Documented 的註解必須設定 Retention 值為 RUNTIME
  • @Inherited
    • 被它修飾的 Annotation 將具有繼承性
    • 如果某個類使用了被 @Inherited 修飾的 Annotation,則其子類將自動具有該註解
    • 如果把標有 @Inherited 註解的自定義的註解標註在類級別上,子類則可以繼承父類類級別的註解

可重複註解、型別註解

JDK8 對註解處理提供了兩點改進:可重複的註解及可用於型別的註解。此外,反射也得到了加強,在 JDK8 中能夠得到方法引數的名稱。這會簡化標註在方法引數上的註解。

可重複的註解

在 JDK8 之前我們不能在一個型別重複使用同一個註解;如果想要這樣使用,要用陣列來實現重複註解。

public @interface MyAnnotation {
    String value();
}

public @interface MyAnnotations {
    MyAnnotation[] value();
}

-------------- 使用 ↓ ----------------

@MyAnnotations({@MyAnnotation("Hello"), @MyAnnotation("World")})
class Person {}

JDK8 之後,在需要重複的註解上宣告@Repeatable,成員值為 MyAnnotations.class,讓這 2 個註解關聯在一起。並且,MyAnnotation 的 @Target@Retention等元註解要與 MyAnnotations 一致。

@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
    String value();
}

public @interface MyAnnotations {
    MyAnnotation[] value();
}

-------------- 使用 ↓ ----------------

@MyAnnotation("World")
@MyAnnotation("Hello")
class Person {}

型別註解

JDK8 之後,關於元註解 @Target 的引數型別 ElementType 列舉值多了 2 個:TYPE_PARAMETERTYPE_USE。在 JDK8 之前,註解只能是在宣告的地方所使用;JDK8 開始,註解可以應用在任何地方。

  • ElementType.TYPE_PARAMETER:表示該註解能寫在型別變數的宣告語句中(如:泛型宣告)
  • ElementType.TYPE_USE:表示該註解能寫在使用型別的任何語句中