Java 註解 (Annotations)

註解是一種後設資料(metadata),可以為程式碼提供額外資訊,用於編譯檢查、程式碼生成、執行時處理等。

內建註解

@Override

public class Child extends Parent {
    @Override  // 確保正確覆寫父類別方法
    public void doSomething() {
        // ...
    }
}

@Deprecated

@Deprecated
public void oldMethod() {
    // 已棄用的方法
}

@Deprecated(since = "1.5", forRemoval = true)
public void veryOldMethod() {
    // 將在未來版本移除
}

@SuppressWarnings

@SuppressWarnings("unchecked")
public void method() {
    List list = new ArrayList();  // 不會產生警告
}

@SuppressWarnings({"unchecked", "deprecation"})
public void anotherMethod() {
    // 抑制多種警告
}

@FunctionalInterface

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
    // 只能有一個抽象方法
}

@SafeVarargs

@SafeVarargs
public final <T> void process(T... items) {
    for (T item : items) {
        System.out.println(item);
    }
}

自訂註解

基本語法

public @interface MyAnnotation {
    String value();
    int count() default 1;
}

// 使用
@MyAnnotation(value = "test", count = 5)
public class MyClass {}

// value 可以省略屬性名
@MyAnnotation("test")
public class MyClass2 {}

元註解

import java.lang.annotation.*;

@Target(ElementType.METHOD)           // 可用於哪些元素
@Retention(RetentionPolicy.RUNTIME)   // 保留到何時
@Documented                            // 包含在 Javadoc
@Inherited                             // 子類別繼承
public @interface MyAnnotation {
    String value();
}

@Target 選項

說明
TYPE類別、介面、列舉、Record
FIELD欄位
METHOD方法
PARAMETER方法參數
CONSTRUCTOR建構子
LOCAL_VARIABLE區域變數
ANNOTATION_TYPE其他註解
PACKAGE套件
TYPE_PARAMETER型別參數
TYPE_USE型別使用

@Retention 選項

說明
SOURCE只在原始碼,編譯後丟棄
CLASS保留到 .class 檔,執行時不可用(預設)
RUNTIME執行時可透過反射取得

執行時讀取註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
    String name() default "";
}

public class MyTest {
    @Test(name = "測試方法1")
    public void test1() {}
    
    @Test
    public void test2() {}
}

// 讀取註解
Class<?> clazz = MyTest.class;
for (Method method : clazz.getDeclaredMethods()) {
    if (method.isAnnotationPresent(Test.class)) {
        Test test = method.getAnnotation(Test.class);
        System.out.println(method.getName() + ": " + test.name());
    }
}

實用範例

驗證註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
    String message() default "不能為空";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
    int min() default 0;
    int max() default Integer.MAX_VALUE;
}

public class User {
    @NotNull
    private String name;
    
    @Range(min = 0, max = 150)
    private int age;
}

// 驗證器
public class Validator {
    public static void validate(Object obj) throws Exception {
        for (Field field : obj.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            
            if (field.isAnnotationPresent(NotNull.class)) {
                if (field.get(obj) == null) {
                    NotNull ann = field.getAnnotation(NotNull.class);
                    throw new IllegalArgumentException(ann.message());
                }
            }
            
            if (field.isAnnotationPresent(Range.class)) {
                Range range = field.getAnnotation(Range.class);
                int value = (int) field.get(obj);
                if (value < range.min() || value > range.max()) {
                    throw new IllegalArgumentException("值必須在 " + range.min() + " 到 " + range.max() + " 之間");
                }
            }
        }
    }
}

可重複註解

@Repeatable(Schedules.class)
public @interface Schedule {
    String day();
    String time();
}

public @interface Schedules {
    Schedule[] value();
}

@Schedule(day = "Mon", time = "09:00")
@Schedule(day = "Wed", time = "14:00")
public class Meeting {}

// 讀取
Schedule[] schedules = Meeting.class.getAnnotationsByType(Schedule.class);

常見框架註解

// Spring
@Component
@Service
@Repository
@Controller
@Autowired
@Value

// JPA
@Entity
@Table
@Id
@Column
@OneToMany

// Jackson
@JsonProperty
@JsonIgnore

// JUnit
@Test
@BeforeEach
@AfterEach

重點整理

  • 註解是後設資料,為程式碼提供額外資訊
  • @Override@Deprecated@SuppressWarnings 是常用內建註解
  • 使用 @interface 定義自訂註解
  • @Target 指定可用位置,@Retention 指定保留時機
  • RUNTIME 保留才能在執行時透過反射讀取
  • 許多框架大量使用註解進行設定