元註解:註解的註解
Java 定義 4 標準元註解(java.lang.annotation 套件),用於描述其他註解的行為:
@Target:註解適用目標
@Target(ElementType.TYPE) // 類、介面、枚舉、註解
@Target(ElementType.FIELD) // 欄位(包含枚舉常數)
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 參數
@Target(ElementType.CONSTRUCTOR) // 建構子
@Target(ElementType.LOCAL_VARIABLE) // 區域變數
@Target(ElementType.ANNOTATION_TYPE) // 註解類型
@Target(ElementType.PACKAGE) // 套件
@Target(ElementType.TYPE_PARAMETER) // 型別參數 (Java 8+)
@Target(ElementType.TYPE_USE) // 型別使用 (Java 8+)
@Retention:生命週期策略
| 策略 | 保留範圍 | 典型用途 |
RetentionPolicy.SOURCE | 僅原始碼,編譯器捨棄 | @Override, @SuppressWarnings, @Generated |
RetentionPolicy.CLASS | Class 檔案,JVM 不載入(預設) | 編譯期處理、字節碼掃描 |
RetentionPolicy.RUNTIME | 運行時可透過反射獲取 | 框架整合、依賴注入、AOP、序列化 |
@Documented:文檔化標記
- 標記註解,無屬性
- 被標註的註解會出現在 Javadoc 生成的文檔中
- 公共 API 註解建議加上
@Inherited:繼承性標記
- 類別繼承時,子類別自動獲得父類別的該註解
- 僅對類別有效,介面實現、方法覆寫不生效
- 配合
RUNTIME 保留,反射 API 會遞歸查找父類別
自定義註解語法規範
聲明格式
public @interface AnnotationName {
// 屬性定義 = 無參方法
// 預設值用 default
String value() default "";
int count() default 0;
Class<?> clazz() default Object.class;
Color color() default Color.RED;
String[] tags() default {};
}
屬性類型限制
- 8 種基本型別
String
Class
enum
- 註解類型(支援巢狀)
- 以上類型的陣列
- 禁止:物件、集合、泛型、可變型別
存取修飾符
- 僅允許
public 或 default(包私有)
- 慣例使用
default(無修飾符)
單屬性慣例
// 單屬性命名為 value,使用時可省略屬性名
@FunctionalInterface
public @interface MyAnnotation {
String value() default "";
}
// 使用
@MyAnnotation("hello") // 等同於 @MyAnnotation(value="hello")
完整自定義註解範例
1. 表映射註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {
String name() default ""; // 預設類別名
String schema() default "";
String catalog() default "";
}
2. 欄位映射註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
String name() default ""; // 預設欄位名
boolean nullable() default true;
boolean unique() default false;
int length() default 255;
String columnDefinition() default "";
}
3. 實體類別使用
註解處理器:讓註解發揮作用
運行時反射處理(最常見)
public class AnnotationProcessor {
public void processTable(Object entity) {
Class<?> clazz = entity.getClass();
// 1. 獲取類別註解
Table table = clazz.getAnnotation(Table.class);
String tableName = table != null && !table.name().isEmpty()
? table.name() : clazz.getSimpleName();
// 2. 掃描欄位註解
List columns = new ArrayList<>();
List values = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true); // 存取私有欄位
Column column = field.getAnnotation(Column.class);
if (column == null) continue; // 無註解跳過
String colName = column.name().isEmpty() ? field.getName() : column.name();
Object value;
try {
value = field.get(entity);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
columns.add(colName);
values.add(value != null ? "'" + value + "'" : "NULL");
}
// 3. 生成 SQL
String sql = String.format("INSERT INTO %s (%s) VALUES (%s)",
tableName, String.join(", ", columns), String.join(", ", values));
System.out.println(sql);
}
}
編譯期註解處理(APT,進階)
- 實現
javax.annotation.processing.Processor
- 註冊
META-INF/services/javax.annotation.processing.Processor
- 編譯期生成代碼(如 Lombok、MapStruct、Dagger、Room)
- 不影響運行時效能,但開發複雜度高
內建註解最佳實踐
| 註解 | 用途 | 最佳實踐 |
@Override | 方法覆寫檢查 | 所有覆寫方法強制加上,防止拼寫錯誤 |
@Deprecated | 標記過時 | 配合 Javadoc @deprecated 說明替代方案 |
@SuppressWarnings | 抑制編譯警告 | 範圍最小化,僅加在具體語句/方法,避免類別級 |
@SafeVarargs | 可變參數堆污染抑制 | 僅用於確認安全的 final/static 方法 |
@FunctionalInterface | 函數式介面標記 | 所有 SAM 介面加上,編譯器強制檢查 |
常見陷阱與避坑指南
| 陷阱 | 後果 | 解決 |
| @Retention 預設為 CLASS | 運行時反射獲取不到 | 框架整合註解必須显式 @Retention(RUNTIME) |
| @Target 缺失 | 可用於所有位置,語義模糊 | 明確限定適用範圍 |
| 屬性用物件/集合 | 編譯錯誤 | 轉為陣列、字串、枚舉、Class |
| 註解繼承誤區 | 介面實現/方法覆寫不繼承 @Inherited | 理解僅類別繼承生效 |
| 預設值不一致 | 註解處理器邏輯複雜 | 統一預設值語義,文檔化說明 |
總結
註解是 Java 元程式設計的核心。掌握元註解配置、自定義語法規範、反射處理器實現,能構建類型安全、可讀性強、框架友好的 API。運行時反射適合業務邏輯處理,編譯期 APT 適合代碼生成,兩者互補,共同構建現代 Java 生態系統。