Java — 註解 Annotation

Mask
9 min readApr 28, 2023

--

初看Java時, @Override 類似這種註解真的是看不懂,重點是有很多種!
利用一篇文好好來搞懂

Photo by Glen Carrie on Unsplash

註解 Annotation

用於放在類、 函式、變數前的特別 “註釋”,註解根據設定,會被編譯進 class 檔,或是在程式運行中可調用,或是根據工具的定義發揮作用

註解分為三種

  • 編譯器使用:如 @Override@SuppressWarnings ,不會被編譯進 .class ,編譯完後就會被丟棄
  • 工具使用:由工具處理,在編譯時對 class 做動態修改,所以會被編譯進 .class ,如 Spring 的容器註解
  • 程式使用:程式運行中會一直使用,如 PostConstruct 會在調用 Constructor 後自動被調用,因此會被存在 JVM 中

配置參數

在定義註解時可以為註解配置參數,但僅限制可以配置以下類型的參數

  • 基本類型 ( int、long、char、double、float等)
  • String
  • Class
  • Enum (枚舉類)、枚舉數組

需注意註解的配置參數必須是常量,即在編譯時已知,而不是運行時才得知,且配置參數類型需是不變的,比如不能為泛型

定義註解

在 Java 中使用 @interface 來定義註解,並且使用 default 來設定默認值,自訂義的註解需有一個自己的 .java

public @interface Check {
int mix() default 0;

int min() default 100;
}

元註解 meta annotation

用來用在註解上的註解,主要是設置註解的作用區域與生命週期,並有以下四種

  • Retention
  • Target
  • Document — 用來指定被註解的源碼將被包含在 javaDoc中
  • Inherited

Retention

用來指定註解生命週期,即在編譯後是否會被保留,保留的註解在運行時即可透過反射取得

  • RetentionPolicy.SOURCE:源碼保留,編譯時會忽略
  • RetentionPolicy.CLASS:編譯時會執行,但運行時會忽略
  • RetentionPolicy.RUNTIME:運行時保留,可以通過反射取得

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME) // 運行時保留
public @interface Check {
int mix() default 0;

int min() default 100;
}

Target

設置註解的作用對象,有以下數種

  • ElementType.TYPE:類、介面、枚舉類
  • ElementType.FIELD:類中的變數
  • ElementType.METHOD:函式
  • ElementType.PARAMETER:函式的參數
  • ElementType.CONSTRUCTOR:類的構造函式
  • ElementType.LOCAL_VARIABLE:局部變量
  • ElementType.ANNOTATION_TYPE:註解類型
  • ElementType.PACKAGE:Java 文件中的包聲明
  • ElementType.TYPE_PARAMETER:泛型參數
  • ElementType.TYPE_USE:在類型使用時可用
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) // 用於類中的變數
public @interface Check {
int mix() default 0;

int min() default 100;
}

Inherited

定義子類是否可繼承父類的註解,即對 A類使用,B類繼承A類,B類也會繼承該註解,但是 Inherited 只在 ElementType.TYPE ,並針對 class 的繼承時有效,且對自訂義的註解才有作用,Java 內置的註解無效

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited // 在這
public @interface Check {
int mix() default 0;

int min() default 100;
}

具體使用

註解本身對程式邏輯沒有直接影響,需另外實作應用邏輯, SOURCE 在編譯時即被丟棄, CLASS 僅保留在 .class 文件中,需要關注且會頻繁撰寫的只有 RUNTIME

註解在定義後也是一種 class,要獲取註解的內容,可以使用 Java 提供的
反射 API

  • 取得類的註解:Class.getAnnotation(Class)
  • 取得變數的註解:Field.getAnnotation(Class)
  • 取得函式的註解:Method.getAnnotation(Class)
  • 取得函式參數的註解:Method.getParameterAnnotations();
  • 取得構造函式的註解:Constructor.getAnnotation(Class)

檢查變數

範例註解如下

// Check.java
package Test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Check {
int max() default 100;

int min() default 0;
}

主要調用程式如下

// Test.java

package Test;

import java.lang.reflect.Field;

public class Test {
public static void main(String[] args) {
Box b1 = new Box(100, 10);

try {
Box.check(b1);

} catch (IllegalArgumentException e) {
System.out.println("錯誤的參數");

} catch (ReflectiveOperationException e) {
System.out.println("反射錯誤");
}
}

}

class Box {
@Check(max = 99, min = 1)
public int n;

@Check(max = 1000, min = -1)
public int z;

public Box(int n1, int n2) {
this.n = n1;
this.z = n2;
}

static void check(Box box) throws IllegalArgumentException, ReflectiveOperationException {

// 取得 Box類的public變數
for (Field f : box.getClass().getFields()) {

// 取得註解 Check
Check ck = f.getAnnotation(Check.class);

if (ck != null) {

// 透過反射取得f在box物件中的值
// 因為不能確定類型,只能返回根類
Object val = f.get(box);

// int 會被自動裝箱成 Integer,且此處不能用基本類型
if (val instanceof Integer i) {

if (i < ck.min() || i > ck.max()) {
throw new IllegalArgumentException(
"Invalid arg " + f.getName() + " with value "
+ i.toString());
}
}
}
}

}

}

沒有處理 catch 的內容的話,會在終端上顯示

Photo by Kari Shea on Unsplash

還可以看另一篇關於 Spring Boot Annotation 的文章 👉 點我
如果這篇文章有幫助到你,請不吝給我個鼓掌並看看我的其他文章吧😎

使用註解的過程用到大量的反射,需要回頭啃一下反射的文章…
參考教程:廖雪峰的官方網站

--

--