---
title: 'Java 註解'
disqus: kyleAlien
---
Java 註解
===
## Overview of Content
如有引用參考請詳註出處,感謝 :smile:
> Java 註解從 Java 1.5 添加到 Java,以下會使用 Android 註解 & Java 註解
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**深入探討 Android、Java 註解應用:分析註解與 Enum 的差異 | Android APT**](https://devtechascendancy.com/android-annotations-enums-guide/)
:::
[TOC]
## 註解概述
Support Annotation Library 從 19.1 引進全新的函數套件,在開發中加入程式可**提升程式的品質** (可讓編譯器檢查),增加程式碼的健壯
## Android 基礎註解
* Android 中自身有的註解介紹
```java=
// Android gradle 加入註解的依賴
dependencies {
implementation 'com.android.support:support-annotations:28.0.0'
}
```
### Nullness 註解
| 註解名 | 意義 |
| --------- | ---------------------------- |
| @Nullable | 參數 or 回傳值**可以為空** |
| @NonNull | 參數 or 回傳值**不可以為空** |
```java=
public class TestAnnotation {
public static void main(String... args) {
String s1 = useAnnotation("Alien");
String s = useAnnotation(null);
}
private static @NonNull String useAnnotation(@NonNull String str) {
return "Hello World, " + str;
}
}
```
**--實作--**
> 
### 資源類型註解
* 資源在 Android 中通常以整數來代表 (存在於 R.java 中),該註解可以避免資源被放置到錯的參數中
| 註解名 | 意義 |
| ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| @XXXRes | XXX 代表為 android.R.XXX 類型的資源,有 Animator, Anim, String, Array, Bool, Color, Drawable, Integer, Layout, ID...**+Res** |
```java=
public class TestAnnotation {
public static void main(String... args) {
TestRxxx(new Integer[]{});
TestRxxx(new String[]{});
TestRxxx(R.id.response);
}
private static void TestRxxx(@IdRes int id) {
}
}
```
**--實作--**
> 
### 執行緒註解
| 註解名 | 意義 |
| ------------- | --------------------------------------------------------------------------------------------------------------------- |
| @UiThread | 一個應用而言可能存在多個 UiThread,每個 UI 執行緒對應不同視窗 |
| @MainThread | 標記**執行在主線程**,一個應用只有一個主執行緒 (就是 @UiThread 執行緒),大部分情況 @MainThread 使用在生命週期相關函數 |
| @WorkThread | 背景**後台工作**的執行緒 |
| @BinderThread | 標記**執行在 Binder** 的執行緒 |
* `@UiThread` & `@MainThread` 一般來說是可以互換的,因為 UI Thread 通常就是指 Main Thrad(並非一定)
> 像是 Android 的輕量級異步執行緒框架 `AnsyncTask` 就有此用這些
… 有關於 AnsyncTask 解釋請點擊連結了解([**Template 設計模式 | 實現與解說 | Android source AsyncTask 微框架**](https://devtechascendancy.com/object-oriented_design_template/))
```java=
// AnsyncTask.java
@WorkerThread
protected abstract Result doInBackground(Params... params);
...
@MainThread
protected void onPreExecute() {
}
...
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onPostExecute(Result result) {
}
...
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onProgressUpdate(Progress... values) {
}
...
@SuppressWarnings({"UnusedParameters"})
@MainThread
protected void onCancelled(Result result) {
onCancelled();
}
```
### RGB 註解
* RGB / ARGB 每個字母佔 8Byte
> 
* 它跟 @ColorRes 的差別在 **@ColorRes 只接收 Resouce 的資源項目,@ColorInt 接收所有符合的資源內容**
| 註解名 | 意義 |
| --------- | ---------------------------- |
| @ColorInt | 需要傳入 RGB 顏色**整數** |
**--實作--**
> 只要符合 `@ColorInt` 規範的整數就可以 (以下可以看出 ID 類的標明也是符合,但整數 20 不可以)
>
> 
### 註解範圍
| 註解名 | 意義 | Example |
| ----------------- | ----------------------------- | --------------------------- |
| @Size(min=x) | 集合**下限** | @Size(min=1) |
| @Size(max=x) | 集合**上限** | @Size(max=3) |
| @Size(value=x) | **固定**集合數量 | @Size(value=2) |
| @Size(multiple=x) | 集合大小可為 x 的**倍數數量** | @Size(multiple=2) |
| @IntRange(x) | 參數類型可為 int or long | @IntRange(from=0,to=255) |
| @FloatRange(x) | 參數類型可為 float or double | @FloatRange(from=-1.0,to=1.0) |
```java=
public class TestAnnotation {
public static void main(String... args) {
TestSizeMin(new int[]{1,2});
TestSizeMin(new int[]{});
TestSizeMax(new int[]{1,2});
TestSizeMax(new int[]{1,2,3,4});
TestSizeFixed(new int[]{1,2});
TestSizeFixed(new int[]{1,2,3});
TestSizeMultiple(new int[]{1,2});
TestSizeMultiple(new int[]{1,2,3,4,5});
TestIntRange(123);
TestIntRange(256);
TestFloatRange(1.0F);
TestFloatRange(-2.0F);
}
private static void TestSizeMin(@Size(min = 1) int[] params) {
}
private static void TestSizeMax(@Size(max = 3) int[] params) {
}
private static void TestSizeFixed(@Size(value = 2) int[] params) {
}
private static void TestSizeMultiple(@Size(multiple = 2) int[] params) {
}
private static void TestIntRange(@IntRange(from = 0, to = 255) long a) {
}
private static void TestFloatRange(@FloatRange(from = -1.0, to = 1.0) float a) {
}
}
```
* 限定於**陣列**才可判斷,其餘不可判斷
```java=
public class TestAnnotation {
public static void main(String... args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
TestSizeInList(list);
TestSizeOther(1,3,5,6,7,8);
}
private static void TestSizeInList(@Size(3) ArrayList<Integer> list) {
}
private static void TestSizeOther(@Size(3) int... params) {
}
}
```
**--實作--**
> 
### 許可權註解
| 註解名 | 意義 |
| -------------------------------- | ----------------------------------------------- |
| @RequiresPermission() | 提醒使用者要在 AndroidManifest.mxl 中宣告許可權 |
| @RequiresPermission(allof={x,y}) | 以下的權限皆需要 |
| @RequiresPermission(anyOf={x,y}) | 最少需要其中一個 |
### 重新定義函數註解
* Android 的生命週期 onCreate 就有使用;**建構函數不需要** @CallSuper
| 註解名 | 意義 |
| --------- | ---------------------------- |
| @CallSuper | 提醒使用者要複寫該方法 |
```java=
public class TestAnnotation {
public static void main(String... args) {
new RichMan(new PoorMan()).wear();
}
}
abstract class Decorator {
Decorator() {
}
public abstract void wear();
}
class PoorMan extends Decorator {
@Override
public void wear() {
System.out.println("UnderPant");
}
}
abstract class WorkMan extends Decorator {
protected Decorator d;
protected WorkMan(Decorator d) {
this.d = d;
}
@CallSuper
@Override
public void wear() {
d.wear();
}
}
class RichMan extends WorkMan {
protected RichMan(Decorator d) {
super(d);
}
@Override
public void wear() {
super.wear();
System.out.println("Jacket");
System.out.println("Pant");
System.out.println("T-shirt");
System.out.println("sock");
System.out.println("shoes");
}
}
```
以 [**裝飾器模式**](https://hackmd.io/DlDU-niGRg-0p-dw-BqxKQ)來說每個實作類都需要呼叫該父類的方法,但使用者有時會忘記呼叫 `super()` 這時就可以使用,用來提醒使用框架者要記得呼叫父類方法
:::warning
這種提醒機制並「**非強制**」,就算使用者不遵從提醒仍可編譯過
:::
**--實作--**
> 
### 回傳值註解
* 如果某函數需要對 `return` 值作處理這時就可以呼叫 @CheckResult,把要寫的警訊寫在 suggest 中,Android 第三方 Lib 很常使用
| 註解名 | 意義 |
| ------------------------- | ---------------------- |
| @CheckResult(suggest="x") | 提醒使用者要注意回傳值 |
## Enum 與 Annotation 差異
我們在撰寫程式時,常常使用 Enum 來表達出可使用的選項(同時限制使用者只能使用 Enum 中的選項);而使用註解也可以達到告訴使用者該參數能接收的選項
> 同樣注意到的是,這並非是一個硬式限制,更像是一種提醒
### Enum 反編譯解析
* **Enum 就算是一個「類」,而一個類所占用的標頭最少是 12 個字節(byte),所以會耗費更多的記憶體空間**
```java=
class MyTestClass {
enum Level {
LEVEL_1,
LEVEL_2,
LEVEL_3,
LEVEL_4,
LEVEL_5,
LEVEL_6,
}
}
```
上面程式碼經過 ASM 反組譯後會變為,**==每一個成員皆為一個 enum 類==**,所 **佔用空間過大** (Byte 描述 Class 會耗費多一點空間)
>
> 
### Enum vs. Android IntDef
* `@IntDef` 註解由 Androidx 提供,**並標明 `@Target({==ANNOTATION_TYPE==})` 表示它是註解在註解上的註解**
**InDef 可使用在 IDE 檢查,其階段只保留到 ++Source++**,以下是它的源碼
> Android 還有 `StringDef`... 等等可以使用
```java=
// 源碼
@Retention(SOURCE) // 保存到 Source 階段
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}
```
* **`@IntDef` 特性有**:
1. **效能更高**:
在 Android 平台上,使用 enum 會比使用 int 消耗更多的內存,因為 enum 會引入額外的方法和屬性;使用 `@IntDef` 則不會有這個問題,因為它本質上還是使用整數值
2. **編譯期檢查**:
`@IntDef` 提供了編譯期檢查,確保只能使用指定的整數值,避免了使用其他不合法的值
3. **提高可讀性**:
通過 `@IntDef` 註解,可以使代碼更加易讀,因為我們可以定義一組有意義的常量來表示不同的狀態或類型
* **`@IntDef` 的使用方式**:
要使用 **IntDef 需要自定義一個註解,並且標註該註解作用的區域 @Target,還有它的保留階段 `@Retention`**
```java=
class MyTestClass {
//"1. "
@Target({ElementType.FIELD, ElementType.PARAMETER})
@IntDef(value = {LEVEL_1, LEVEL_2, LEVEL_3, LEVEL_4, LEVEL_5})
@Retention(RetentionPolicy.SOURCE)
@interface MyLevel {
}
public static final int LEVEL_1 = 1;
public static final int LEVEL_2 = 2;
public static final int LEVEL_3 = 3;
public static final int LEVEL_4 = 4;
public static final int LEVEL_5 = 5;
public static void main(String []args){
System.out.println("Hello World");
setLevel(1111); //"2. "
setLevel(3);
setLevel(LEVEL_3);
}
public static void setLevel(@MyLevel int level) {
System.out.println("level: " + level);
}
}
```
1. **使用自定義註解,要標示它作用的區域**,如果沒有標示 `ElementType.PARAMETER` 該註解則不能作用於函式參數
2. 放入**非標準的格式++編譯仍然可以過++,但是寫程式時跳出警告訊息,==讓程式更加健壯==**
> 可看出所占用的空間變少,一個標示只佔 4 個字節
>
> 
## Java標準註解
* Java 也可使用,不限定 Android,它定義在 `java.lang`、`java.lang.annotation`、`javax.annotation` 套件中
### 編譯相關註解:SafeVarargs 使用
| 註解名 | 意義 |
| ------------------------------ | ---------------------------------------------- |
| @Override | **覆寫**父類方法,如果並無覆寫則會報錯誤 |
| @Deprecated | 已**遺棄**的方法,編譯器會警告使用者 |
| @SuppressWarnings(value={x,y}) | **抑制**編譯器警告,可抑制多個警告 |
| @SafeVarargs | **支援資料可變長度** |
| @Generated | 一般**用於給程式自動產生代碼**,不建議手動修改 |
| @FuntionalInterface | 該**介面只有一個方法**,可使用在 Lambda |
* **註解 `@SafeVargs` 目的 & 意義**
其主要目的是處理 **==泛型 的可變長參數(T...t)==** ,告編譯器此泛型是**安全長參數**
可變長參數使用數組儲存,而數組 & 泛型不能很好的混合使用
:::warning
* **數組 & 泛型 ?** 可以使用泛型數組不會有警告
**數組**:編譯期間就已確定(靜態確認)
**泛型**:運行時才能確定數據,因此編譯時無法判別其正確性,導致產生警告(動態確認)… 這時我們就可以使用 `@SafeVargs` 去壓制警告
:::
:::danger
* **註解 `@SafeVargs` 只能宣告在 ==static== or ==final== 的函數**
> 
:::
* 對於 `SafeVarargs`、`SuppressWarnings` 使用範例如下
```java=
public class TestProblemOfGeneric {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
//"2. "
FixList.add2List(list1, "Pan", "Kyle", "Alien");
FixList.add2List(list2, "One", "Two", "Three");
List<List<String>> list3 = new ArrayList<>();
//"3-1. "
FixList.add2List(list3, list1, list2);
//"3-2. "
FixList.TestMethod(Arrays.asList("Hello!"),
Arrays.asList("World!"));
}
}
class FixList {
public static <T> void add2List(List<T> list, T...data) {
for(T t : data) {
list.add(t);
}
}
//"1. "
public static void TestMethod(List<String>...list) {
Object[] o = list;
//"4. "
o[0] = Arrays.asList(new Integer(123));
String str = list[0].get(0);
System.out.println("Test: " + str);
}
}
```
1. `...` 代表可變的參數長度,編譯器會將其變成 **數組**,也就是說 `List<String>...list` 可以看做 `List<String>[]`
2. 一般類型可正常轉為可變長度,再轉為數組
3. Java 不允許泛型類類型的數組 (++無法確認數據類型++,會發出警告),**使用 `@SafeVarargs` 使其安全使用 (確定不會錯誤時再添加這個註解)**
* `3-1`:`Object[] o = list` 這個語句會轉化為 `Object[] o = (List[]) list;` 這是正確的
但 `Arrays.asList(new Integer(123))`,就類型錯誤會拋出 `ClassCastException` 異常
* `3-2`:Arrays.asList 方法返回的是泛型數組
4. 如果沒有 `@SafeVarargs` 註解壓住錯誤的話,IDE 會警告發生 [**HashPollution**](https://hackmd.io/Dlm0Ov0URya2t6mbTgNQkQ#Heap-Pollution) 錯誤(想了結詳情情點擊連結)
> 
### Java 資源註解
* 一般用於 Java EE (Java EnterPrise),Android 較少使用
| 註解名 | 意義 |
| -------------- | ---------------------------------------------------------- |
| @PostConstruct | 用在控制物件生命週期的環境中,在呼叫建構函數後應立即被呼叫 |
| @PreDestory | 同上,在銷毀物件之前應被呼叫的方法 |
| @Resoure | 用於 Web 容器的資源植入,單一資源 |
| @Resoures | 同上,表示陣列資源 |
### Java 自定義註解
* 透過 ==**@interface**== 關鍵字自定義註解,其形式與接口類似,不過多了 ==@== 符號,可當作是**標籤**,可在每個類、參數、回傳值上貼上標籤
```java=
// Sample 1
@interface HelloWorld {
}
@HelloWorld
class MyAnnotation {
}
// Sample 2 in Android
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface MyAnnotation {
@IdRes int id();
}
@MyAnnotation(id = R.id.sample_text)
TextView tv;
```
### Java 元註解
* 可理解為 ==**++註解的註解++**,不能修飾一般方法==,可應用在其他註解上,分為五種…
1. **@Retention (註解保留期間)**(Java 1.5 加入)
> 選項在 RetentionPolicy 中 (emum 類)
| 元註解名 | 保留期間 | 使用 |
| ------ | ----- | ----- |
| RetentionPolicy.SOURCE | 註解只留在源碼中,編譯過後即捨棄 (保存到 .java 為止) | IDE、APT |
| RetentionPolicy.CLASS | 註解留到編譯進行時,並**不會加載到 JVM 中** (保存到 .class 為止) | AOT 思想,插件化 |
| RetentionPolicy.RUNTIME | 註解留到 runtime,**會加載到 JVM 中**,所以反射可以使用 (保存在 JVM 中) | 反射 |
> 根據元註解 (Retention) 不同有不同效果
>
* Source 編譯器可 **++使用註解偵錯、警告,APT++**
> 
>
> 
* Class 編譯階段,可使用 **++字節碼插入++**,但並不會保留到運行期間(也就是你可以在 `.class` 階段看到它的存在,但運行時就會消失)
> 
>
> 
* Runtime 註解:可在 Runtime 時提取 **++反射++**,這時的註解 ==**也會代碼的一部分**==,它會保留到運行期間
> Runtime 註解通常使用在框架中
:::danger
如果是開發 Android 或其他專案時使用到 Runtime 反射機制,**要特別注意「混淆機制」,混淆會導致反射運作不正常!** 要注意去寫混肴文件,跳過混淆
:::
2. **@Target (目標)**:**註解所修飾的對象範圍**(Java 1.5 加入)
> 選項在 ElementType 中 (emum 類),`import java.lang.annotation.RetentionPolicy;`
| 元素 | 適用 |
| --------------------------- | ---------------------------- |
| ElementType.ANNOTATION_TYPE | 註解型態宣告(給註解貼上標籤) |
| ElementType.CONSTRUCTOR | 註解建構函數 |
| ElementType.FIELD | 註解屬性 |
| ElementType.LOCAL_VARIABLE | 註解區域變數 |
| ElementType.METHOD | 註解方法 |
| ElementType.PACKAGE | 註解包 |
| ElementType.PARAMETER | 註解方法參數 |
| ElementType.TYPE | 類別 or 介面 |
| ElementType.USE | 類型的用途 |
**可註解多個,使用 `{}` 包覆數值**,範例如下…
```java=
@Target({
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.TYPE}
)
```
3. **@Documented**(Java 1.5 加入)
> 將註解中的元素包含入 JavaDoc 中
4. **@Inherited (繼承)**(Java 1.5 加入)
> 該註解可以被繼承 (不是說註解被繼承),如果父類使用過註解,子類沒有使用,則該子類就會繼承該父類註解
5. **@Repeatable (重複)**(Java 1.8 加入)
> 該註解可多次使用
### 自定義註解的屬性
* 在我們自定義註解時,內部 **++只可以有屬性++**,**不能有方法**!
> 而註解中的 value 是一個特殊的屬性,value 不需要指定
```java=
public @interface MyAnnnotation {
// value 接收 String 類型參數
String value();
// StrArray 接收 string array 類型
String[] StrArray(); // 需要指定
}
```
* 自定義註解中的屬性包含基礎的 8 個類還有 3 個特殊類,分別是…
* 基礎類型:`byte`, `short`, `int`, `long`, `float`, `double`, `char`, `boolean`
* 特殊類型:`Class`、`enum`、`interface`
```java=
//"1. " 使用預設質、或指定
@MyAnnnotation(id=10, name="ABC", getEnum = MyEnum.THREE)
class ToDo {
}
interface MyInterface {
}
enum MyEnum {
ONE, TWO, THREE
}
@interface MyAnnnotation {
int id() default -1;
String name() default "";
//"2. " 不能設定方法
//void setAddress(String addr);
Class<?> get() default Todo.class;
MyEnum getEnum() default MyEnum.ONE;
enum TestEnum {
ONE, TWO, THREE
}
interface Test {
}
}
```
## 註解提取
Android 中最常使用到的就是 APT 註解解析器,解析使用者所定義的註解
### RUNTIME
* RUNTIME 反射提取,可以猜考 [**Java 反射篇**](https://hackmd.io/cuMlqQZ4QcujENqlkA1Zrw?view#註解),像是 Gson 源碼就有使用到 (包括泛型反射)
### CLASS
* 用於插件化,AOT 思想,字節碼插入程式的技術,而要插入的點可以透過註解打上標記(這裡有機會我再補充文章,其思想概念是在編譯時插入 ByteCode)
### SOURCE
* **APT 是作用在 Source 過程中,Java 在編譯 java 檔案前會先儲存註解,而處理該註解的則稱為註解處理器**
```mermaid
graph LR
java_f(.java file) --> |執行編譯| 處理註解
subgraph 編譯
class_f(.class file)
處理註解 --> 拋棄註解 --> |產出| class_f
end
```
:::info
`.java` File -> 儲存註解 -> 處理註解 -> 拋棄註解 -> `.class` File
:::
1. 建立一個 MyAnnotation 的註解,並**用元注解標註 ++SOURCE++、++FIELD++**
```java=
package com.oo.jnidemo; // 之後會使用到
...
@Retention(RetentionPolicy.SOURCE) // 只保存在源碼階段
@Target(ElementType.FIELD)
public @interface MyAnnotation {
@IdRes int id();
}
```
2. 新建 **Java or Kotlin Library** (File > New > New Module...),並取名為 annotations
> 
>
這個 Module 作用是 ***註解處理器***,**在 processor Module 的 build.gradle 添加依賴 (implementation)**
```java=
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotations')
}
sourceCompatibility = "7"
targetCompatibility = "7"
```
在該 Library 下創建一個類,取名為 ClassProcessor,**並==繼承 AbstractProcessor==**,並覆寫以下表格的方法 (目前為了測試是在 source 所以只使用 process 函數)
| 方法名稱 | 功能 | 參數功能 |
| --------------------------- | ---------------------------------------------------- | --------------------------------------------- |
| init | 將會被註解處理工具調用 | ProcessingEnvironment 提供工具類 |
| process | 每個**處理器的主函數** | RoundEnvironment 可以查詢特定註解的被註解元素 |
| getSupportedAnnotationTypes | **必須指定的方法,==指定該註解處理器是註冊給哪個註解==**,也**可以使用註解 `@SupportedAnnotationTypes`(==全類名==)** | Non |
| getSupportedSourceVersion | Java 版本 | Non |
```java=
//"a. "
@SupportedAnnotationTypes("com.oo.jnidemo.MyAnnotation")
public class MyAnnotationProcess extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//"b. "
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "!!!======== Annotation Processor ========!!!");
return false;
}
}
```
* a. getSupportedAnnotationTypes 返回的是一個字符串的集合,它包含本處理器將==要處理的註解類型的 **++合法全名++**==,可使用 Class 方法的 getCanonicalName
* b. 輸出到 console 視窗,**因為創建時是使用 Java Lib,所以不能使用 Log,要使用 Message**,並使用 `Diagnostic.Kind.NOTE`
3. ==**註冊「註解處理器」**==,**就像是 Activity 要在 AndroidManifest;註解處理器也要註冊**,有兩種方法 **手動 & 自動**:
以下說明「Google 提供的自動註冊註解 Library」的使用,Google 開源 `AutoService` (可能版本間容不好,要小心使用,但速度較快)
> 在 module processor's gradle 下新增,**annotationProcessor**
```java=
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotations')
// new
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc2'
}
```
> 
>
在註解處理器下**新增 `@AutoService(Process.class)` 的註解**
```java=
// 自動註冊時,Processor.class 這是固定寫法
@AutoService(Processor.class)
// 代表了該註解處理器會處理這個註解 (包名 + 全類名)
@SupportedAnnotationTypes("xxx.yyy.zzz")
// 指定 JDK 編譯版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 接收的參數
@SupportedOptions({"xxx", "yyy"})
public class ClassProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
...
}
}
```
4. **使用註解**:使用的主工程項目中,**添加要使用的 project 依賴**(這個 project 就是我們在上面寫的註解處理器),之後主工程在編譯時,**指定的註解就會被分析**
```java=
dependencies {
...
implementation project(':annotations') // 註解
annotationProcessor project(':processor') // 註解處理器
}
```
在主項目中使用註解
```java=
public class MainActivity extends AppCompatActivity {
@MyAnnotation(id = R.id.sample_text)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
```
最後 **Rebuild project,在 Build 視窗可以看到 `Task **:app:compileDebugJavaWithJavac`,Javac 在編譯 .java 文件,它在處理註解**
**--結果--**
> 
## Appendix & FAQ
:::info
做 CLASS 編譯註解時出點問題,無法正常找到 AutoService
> Ans : 多加入 compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
APT 為何必須 Clear Project or ReBuild Project?
> 因為 APT 自動生成的代碼並不會刪除,clearProject 可以清除生成的 Code
:::
###### tags: `Java 基礎進階` `Android 進階`