初看Java時, @Override
類似這種註解真的是看不懂,重點是有很多種!
利用一篇文好好來搞懂
註解 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
的內容的話,會在終端上顯示
還可以看另一篇關於 Spring Boot Annotation 的文章 👉 點我
如果這篇文章有幫助到你,請不吝給我個鼓掌並看看我的其他文章吧😎
使用註解的過程用到大量的反射,需要回頭啃一下反射的文章…
參考教程:廖雪峰的官方網站