java註解

自在现实發表於2024-06-25

Java-註解
轉載宣告:
本文大量內容系轉載自以下文章,有刪改,並參考其他文件資料加入了一些內容:

最通俗易懂的java註解講解
作者:frank909
來源:騰訊雲
轉載僅為方便學習檢視,一切權利屬於原作者,本人只是做了整理和排版,如果帶來不便請聯絡我刪除。
摘要
Annotation中文譯過來就是註解、標釋的意思,在 Java 中註解是一個很重要的知識點,但經常還是有點讓新手不容易理解。

我個人認為,比較糟糕的技術文件主要特徵之一就是:用專業名詞來介紹專業名詞。
比如:

Java 註解用於為 Java 程式碼提供後設資料。作為後設資料,註解不直接影響你的程式碼執行,但也有一些型別的註解實際上可以用於這一目的。Java 註解是從 Java5 開始新增到 Java 的。

以上是大多數網站上對於 Java 註解,解釋確實正確,但很難理解。

標籤是一張便利紙,標籤上的內容可以自由定義。常見的如貨架上的商品價格標籤、圖書館中的書本編碼標籤、實驗室中化學材料的名稱類別標籤等等。並且,往抽象地說,標籤並不一定是一張紙,它可以是對人和事物的屬性評價。也就是說,標籤具備對於抽象事物的解釋。所以,基於如此,我完成了自我的知識認知升級,我決定用標籤來解釋註解。

標籤

0x01 引子-註解如同標籤
之前某新聞客戶端的評論有蓋樓的習慣,於是 “賈伯斯重新定義了手機,老L重新定義了傻X” 就經常極為工整地出現在了評論樓層中,在某些網友眼中,老L就成了XX的代名詞。

廣大網友給賈伯斯貼了一個名為“傻X”的標籤,他們並不真正瞭解老L,但是因為“傻X”這樣的標籤存在,這有助於他們直接快速地對這個人做出評價。然後基於此,老L就可以成為茶餘飯後的談資,這就是標籤的力量。

而在網路的另一邊,老L靠他的人格魅力自然收穫一大批忠實的擁泵,他們貼的又是另一種標籤。
老羅
老L還是老L,但是由於人們對於它貼上的標籤不同,所以造成對於他的看法大相徑庭,不喜歡他的人整天在網路上評論抨擊嘲諷,而崇拜欣賞他的人則會願意掙錢購買錘子手機的釋出會門票。

我們可以抽象概括一下,標籤是對事物行為的某些角度的評價與解釋。

到這裡,終於可以引出本文的主角註解了。

初學者可以這樣理解註解:想像程式碼具有生命,註解就是對於程式碼中某些鮮活個體的貼上去的一張標籤。簡化來講,註解如同一張標籤。

0x02 註解語法
2.1 概述
同 classs 和interface 一樣,註解也屬於一種型別,它是在 Java SE 5.0 版本中開始引入的概念。

2.2 註解的定義
註解透過 @interface 關鍵字進行定義:

public @interface TestAnnotation {}
1
它的形式跟介面很類似,不過前面多了一個 @ 符號。上面的程式碼就建立了一個名字為 TestAnnotaion 的註解。

你可以簡單理解為建立了一張名字為 TestAnnotation 的標籤。

2.3 註解的應用
上面建立了一個註解,那麼註解的的使用方法是什麼呢。

@TestAnnotation
public class Test {
}
1
2
3
建立一個類 Test,然後在類定義的地方加上 @TestAnnotation 就可以用 TestAnnotation 註解這個類了。

你可以簡單理解為將 TestAnnotation 這張標籤貼到 Test 這個類上面。

不過,要想註解能夠正常工作,還需要介紹一下一個新的概念那就是元註解。

2.4 元註解
2.4.1 基本概念
元註解是可以註解到註解上的註解,或者說元註解是一種基本註解,但是它能夠應用到其它的註解上面。

如果難於理解的話,你可以這樣理解。元註解也是一張標籤,但是它是一張特殊的標籤,它的作用和目的就是給其他普通的標籤進行解釋說明的。

元標籤有 @Retention、@Documented、@Target、@Inherited、@Repeatable5 種,位於java.lang.annotation。

2.4.2 @Retention
Retention的英文意為保留期的意思。顧名思義,當 @Retention 應用到一個註解上的時候,它解釋說明了這個註解的的存活時間。取值如下:

RetentionPolicy.SOURCE
註解只在原始碼階段保留,在編譯時它將被編譯器丟棄。
RetentionPolicy.CLASS (預設)
註解被編譯器在編譯階段寫入位元組碼檔案,但它並不會在執行時被載入到 JVM 中。
RetentionPolicy.RUNTIME
註解被編譯器在編譯階段寫入位元組碼檔案,而且會在執行時載入到 JVM 中,所以在程式執行時可以獲取到它們。
我們可以這樣的方式來加深理解,@Retention 去給一張標籤解釋的時候,它指定了這張標籤張貼的時間。@Retention 相當於給一張標籤上面蓋了一張時間戳,時間戳標明瞭標籤張貼的時間週期。

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
1
2
3
上面的程式碼中,我們指定 TestAnnotation標籤 可以在程式執行期被獲取。

這裡說的執行期獲取,是指類似如下程式碼,如果用RetentionPolicy.CLASS 則獲取不到,用RetentionPolicy.RUNTIME 才能獲取

if (field.isAnnotationPresent(Required.class)) {
}
1
2
2.4.3 @Documented
顧名思義,這個元註解肯定是和文件有關。它的作用是能夠將註解中的元素包含到 Javadoc 中去。

2.4.4 @Target
Target 是目標的意思,@Target指定了註解運用的範圍。 如果@Target元註解不存在,那麼該註解就可以使用在任何程式元素之上。

類比到標籤,原本標籤是你想張貼到哪個地方就到哪個地方,但是因為 @Target 的存在,它張貼的地方就非常具體了,比如只能張貼到方法上、類上、方法引數上等等。

@Target 有下面的取值

ElementType.ANNOTATION_TYPE 可以給一個註解進行註解
ElementType.CONSTRUCTOR 可以給構造方法進行註解
ElementType.FIELD 可以給屬性進行註解
ElementType.LOCAL_VARIABLE 可以給本地變數進行註解
ElementType.METHOD 可以給方法進行註解
ElementType.PACKAGE 可以給一個包進行註解
ElementType.PARAMETER 可以給一個方法內的引數進行註解
ElementType.TYPE 可以給一個型別進行註解,如類,介面或列舉
ElementType.TYPE_PARAMETER 表示該註解能寫在型別變數的宣告語句中
ElementType.TYPE_USE 表示該註解能寫在使用型別的任何語句中(eg:宣告語句、泛型和強制轉換語句中的型別)
2.4.5 @Inherited
Inherited是繼承的意思,但是它並不是說註解本身可以繼承,而是說如果一個超類使用了 @Inherited 註解過的註解,那麼如果它的子類沒有被任何註解應用的話,那麼這個子類就繼承了超類的註解。
說的比較抽象,程式碼來解釋:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
1
2
3
4
5
6
註解 Test 被 @Inherited 修飾,之後類 A 被 Test 註解,類 B 繼承 A,則類 B 也擁有繼承自類A 的 Test 這個註解。

對應到標籤,可以這樣理解:
老子非常有錢,所以人們給他貼了一張標籤叫做富豪。
老子的兒子長大後,只要沒有和老子斷絕父子關係,雖然別人沒有給他貼標籤,但是他自然也是富豪。
老子的孫子長大了,自然也是富豪。
這就是人們口中戲稱的富一代,富二代,富三代。雖然叫法不同,好像好多個標籤,但其實事情的本質也就是他們都有一張共同的標籤,也就是繼承自老子身上的那張富豪標籤。

2.4.6 @Repeatable
Repeatable 自然是可重複的意思。@Repeatable 是 Java 1.8 才加進來的,所以算是一個新的特性。

什麼樣的註解會多次應用呢?通常是註解的值可以同時取多個。

舉個例子,一個人他既是程式設計師又是產品經理,同時他還是個畫家。

@interface Persons {
Person[] value();
}

@Repeatable(Persons.class)
@interface Person{
String role default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
注意上面的程式碼,@Repeatable 註解了 Person。而 @Repeatable 後面括號中的類相當於一個容器註解。

什麼是容器註解呢?就是用來存放其它註解的地方。它本身也是一個註解。

我們再看看程式碼中的相關容器註解。

@interface Persons {
Person[] value();
}
1
2
3
按照規定,它裡面必須要有一個 value 的屬性,屬性型別是一個被 @Repeatable 註解過的註解陣列,注意它是陣列。

如果不好理解的話,可以這樣理解。Persons 是一張總的標籤,上面貼滿了 Person 這種同型別但內容不一樣的標籤。把 Persons 給一個 SuperMan 貼上,相當於同時給他貼了程式設計師、產品經理、畫家的標籤。

我們可能對於 @Person(role=”PM”) 括號裡面的內容感興趣,它其實就是給 Person 這個註解的 role 屬性賦值為 PM ,大家不明白正常,馬上就講到註解的屬性這一塊。

2.5 註解的屬性
註解的屬性也叫做成員變數。註解只有成員變數,沒有方法。註解的成員變數在註解的定義中以無形參的方法形式來宣告,其方法名定義了該成員變數的名字,其返回值定義了該成員變數的型別。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
1
2
3
4
5
6
上面程式碼定義了 TestAnnotation 這個註解中擁有 id 和 msg 兩個屬性。在使用的時候,我們應該給它們進行賦值。

賦值的方式是在註解的括號內以 屬性名=value 形式,多個屬性之前用,隔開:

@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
1
2
3
需要注意的是,在註解中定義屬性時它的型別必須是 8 種基本資料型別外加 類、介面、註解及它們的陣列。

註解中屬性可以有預設值,預設值需要用 default 關鍵值指定。比如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
1
2
3
4
5
6
即規定TestAnnotation 中 id 屬性預設值為 -1,msg 屬性預設值為 Hi。

因為有預設值,所以無需要再在 @TestAnnotation 後面的括號裡面進行賦值了:

@TestAnnotation()
public class Test {}
1
2
另外,還有一種情況。如果一個註解內僅僅只有一個名字為 value 的屬性時,應用這個註解時可以直接將屬性值填寫到括號內。比如Spring的@Component註解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
String value() default "";
}
1
2
3
4
5
6
上面程式碼中,Component 這個註解只有 value 這個屬性。所以可以這樣使用,不需加value:

@Component("test")
public class Test
{}
1
2
3
最後,還需要注意的一種情況是一個註解沒有任何屬性。比如

public @interface Perform {}
1
那麼在應用這個註解的時候,括號都可以省略。

@Perform
public void testMethod(){}
1
2
0x03 Java預置註解
學習了上面相關的知識,我們已經可以自己定義一個註解了。其實 Java 語言本身已經提供了幾個現成的註解。

3.1 @Deprecated
這個元素是用來標記過時的元素,想必大家在日常開發中經常碰到。編譯器在編譯階段遇到這個註解時會發出提醒警告,告訴開發者正在呼叫一個過時的元素比如過時的方法、過時的類、過時的成員變數。

public class DeprecatedTest {
@Deprecated
public void say(){
System.out.println("Noting has to say!");
}
public void speak(){
System.out.println("I have a dream!");
}
}
1
2
3
4
5
6
7
8
9
定義了一個 DeprecatedTest 類,它有兩個方法 say() 和 speak(),其中 say() 被 @Deprecated 註解。然後我們在 IDE 中分別呼叫它們。

在這裡插入圖片描述

可以看到,say() 方法上面被一條直線劃了一條,這其實就是編譯器識別後的提醒效果。

3.2 @Override
這個大家應該很熟悉了,提示該方法是子類複寫的父類方法

3.3 @SuppressWarnings
阻止警告的意思。之前說過呼叫被 @Deprecated 註解的方法後,編譯器會警告提醒,而有時候開發者會忽略這種警告,他們可以在呼叫的地方透過 @SuppressWarnings 達到目的。

@SuppressWarnings("deprecation")
public void test1(){
DeprecatedTest dt = new DeprecatedTest();
dt.say();
dt.speak();
}
1
2
3
4
5
6
具體用法列表如下:

名稱 含義
all to suppress all warnings (抑制所有警告)
boxing to suppress warnings relative to boxing/unboxing operations(抑制裝箱、拆箱操作時候的警告)
cast to suppress warnings relative to cast operations (抑制對映相關的警告)
dep-ann to suppress warnings relative to deprecated annotation(抑制啟用註釋的警告)
deprecation to suppress warnings relative to deprecation(抑制過期方法警告)
fallthrough to suppress warnings relative to missing breaks in switch statements(抑制確在switch中缺失breaks的警告)
finally to suppress warnings relative to finally block that don’t return (抑制finally模組沒有返回的警告)
hiding to suppress warnings relative to locals that hide variable()
incomplete-switch to suppress warnings relative to missing entries in a switch statement (enum case)(忽略沒有完整的switch語句)
nls to suppress warnings relative to non-nls string literals(忽略非nls格式的字元)
null to suppress warnings relative to null analysis(忽略對null的操作)
rawtypes to suppress warnings relative to un-specific types when using generics on class params(使用generics時忽略沒有指定相應的型別)
restriction to suppress warnings relative to usage of discouraged or forbidden references
serial to suppress warnings relative to missing serialVersionUID field for a serializable class(忽略在serializable類中沒有宣告serialVersionUID變數)
static-access to suppress warnings relative to incorrect static access(抑制不正確的靜態訪問方式警告)
synthetic-access to suppress warnings relative to unoptimized access from inner classes(抑制子類沒有按最優方法訪問內部類的警告)
unchecked to suppress warnings relative to unchecked operations(抑制沒有進行型別檢查操作的警告)
unqualified-field-access to suppress warnings relative to field access unqualified (抑制沒有許可權訪問的域的警告)
unused to suppress warnings relative to unused code (抑制沒被使用過的程式碼的警告)
3.4 @SafeVarargs
引數安全型別註解。它的目的是提醒開發者不要用引數做一些不安全的操作,它的存在會阻止編譯器產生 unchecked 這樣的警告。它是在 Java 1.7 的版本中加入的。

@SafeVarargs // Not actually safe!
static void m(List… stringLists) {
Object[] array = stringLists;
List tmpList = Arrays.asList(42);
array[0] = tmpList; // Semantically invalid, but compiles without warnings
String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
上面的程式碼中,編譯階段不會報錯,但是執行時會丟擲 ClassCastException 這個異常,所以它雖然告訴開發者要妥善處理,但是開發者自己還是搞砸了。

Java 官方文件說,未來的版本會授權編譯器對這種不安全的操作產生錯誤警告。

3.5 @FunctionalInterface
函式式介面註解,這個是 Java 1.8 版本引入的新特性。函數語言程式設計很火,所以 Java 8 也及時新增了這個特性。

函式式介面 (Functional Interface) 就是一個具有一個方法的普通介面。
比如

@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface Runnable is used
* to create a thread, starting the thread causes the object's
* run method to be called in that separately executing
* thread.
*


* The general contract of the method run is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我們進行執行緒開發中常用的 Runnable 就是一個典型的函式式介面,上面原始碼可以看到它就被 @FunctionalInterface 註解。

可能有人會疑惑,函式式介面標記有什麼用,這個原因是函式式介面可以很容易轉換為 Lambda 表示式。這是另外的主題了,有興趣的同學請自己搜尋相關知識點學習。

0x04 註解與反射
4.1 基本概念
博文前面的部分講了註解的基本語法,現在是時候檢測我們所學的內容了。

我透過用標籤來比作註解,前面的內容是講怎麼寫註解,然後貼到哪個地方去,而現在我們要做的工作就是檢閱這些標籤內容。 形象的比喻就是你把這些註解標籤在合適的時候撕下來,然後檢閱上面的內容資訊。

要想正確檢閱註解,離不開一個手段,那就是反射。註解透過反射獲取。

4.2 類註解示例
1.首先可以透過 Class 物件的 isAnnotationPresent() 方法判斷它是否應用了某個註解:

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
1
2.然後透過 getAnnotation() 方法來獲取指定型別的註解物件:

public A getAnnotation(Class annotationClass) {}
1
或者是 getAnnotations() 方法返回註解到這個元素(當前類)上的所有註解。

public Annotation[] getAnnotations() {}
1
3.如果獲取到的 Annotation 如果不為 null,則就可以呼叫它們的屬性方法了。比如

@TestAnnotation(message = "apple")
public class Test
{
public static void main(String[] args)
{
if(Test.class.isAnnotationPresent(TestAnnotation.class)){
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.message());
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
程式的執行結果是:

id:1
message:apple
1
2
其中id=1為TestAnnotation預設值,message:apple為我們在使用@TestAnnotation註解時指定的值。

4.3 方法和屬性註解示例
上面的例子中,只是檢閱出了註解在類上的註解,其實屬性、方法上的註解照樣是可以的。

@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
String value();
}

@Retention(RetentionPolicy.RUNTIME)
public @interface Perform {}

@TestAnnotation(msg="hello")
public class Test {
@Check(value="hi")
int a;
@Perform
public void testMethod(){}
@SuppressWarnings("deprecation")
public void test1(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
//獲取類的註解
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
try {
Field a = Test.class.getDeclaredField("a");
a.setAccessible(true);
//獲取一個成員變數上的註解
Check check = a.getAnnotation(Check.class);
if ( check != null ) {
System.out.println("check value:"+check.value());
}
Method testMethod = Test.class.getDeclaredMethod("testMethod");
if ( testMethod != null ) {
// 獲取方法中的註解
Annotation[] ans = testMethod.getAnnotations();
for( int i = 0;i < ans.length;i++) {
System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName());
}
}
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
它們的結果如下:

id:1
message:chengc
check value:hi
method testMethod annotation:Perform
1
2
3
4
需要注意的是,如果一個註解要在執行時被成功運用,那麼 @Retention(RetentionPolicy.RUNTIME) 是必須的。

0x05 註解的使用場景
用標籤來類比註解只是手段,而不是目的。為的是讓大家在初次學習註解時能夠不被那些抽象的新概念搞懵。既然現在,我們已經對註解有所瞭解,我們不妨再仔細閱讀官方最嚴謹的文件:

註解是一系列後設資料,它提供資料用來解釋程式程式碼,但是註解並非是所解釋的程式碼本身的一部分。註解對於程式碼的執行效果沒有直接影響。

註解有許多用處,主要如下:

提供資訊給編譯器: 編譯器可以利用註解來探測錯誤和警告資訊
編譯階段時的處理: 軟體工具可以用來利用註解資訊來生成程式碼、Html文件或者做其它相應處理。
執行時的處理: 某些註解可以在程式執行的時候接受程式碼的提取
值得注意的是,註解不是程式碼本身的一部分。

如果難於理解,可以這樣看。老L還是老L,不會因為某些人對於他“傻x”的評價而改變。也就是說,標籤只是某些人對於其他事物的評價,但是標籤不會改變事物本身。所以,註解同樣無法改變程式碼本身,註解只是某些工具的的工具。

還是回到官方文件的解釋上,註解主要針對的是編譯器和其它工具軟體(SoftWare tool)。當開發者使用了Annotation 修飾了類、方法、Field 等成員之後,這些 Annotation 不會自己生效,必須由開發者提供相應的程式碼來提取並處理 Annotation 資訊。這些處理提取和處理 Annotation 的程式碼統稱為 APT(Annotation Processing Tool)。

現在,我們可以給自己答案了,註解有什麼用?給誰用?
答:給編譯器或者 APT 用的。

0x06 註解完整例子
我要寫一個測試框架,測試程式設計師的程式碼有無明顯的異常。

6.1 引子
—— 程式設計師 A : 我寫了一個類,它的名字叫做 NoBug,因為它所有的方法都沒有錯誤。
—— 我:自信是好事,不過為了防止意外,讓我測試一下如何?
—— 程式設計師 A: 怎麼測試?
—— 我:把你寫的程式碼的方法都加上 @Jiecha 這個註解就好了。
—— 程式設計師 A: 好的。

6.2 例子
6.2.1 註解類Jiecha
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Jiecha {
}
1
2
3
4
5
6.2.2 使用者程式碼
public class NoBug {
@Jiecha
public void suanShu(){
System.out.println("1234567890");
}
@Jiecha
public void jiafa(){
System.out.println("1+1="+1+1);
}
@Jiecha
public void jiefa(){
System.out.println("1-1="+(1-1));
}
@Jiecha
public void chengfa(){
System.out.println("3 x 5="+ 3*5);
}
@Jiecha
public void chufa(){
System.out.println("6 / 0="+ 6 / 0);
}
@Jiecha
public void ziwojieshao(){
System.out.println("我寫的程式沒有 bug!");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
6.2.3 測試程式碼
import java.lang.reflect.Method;
public class TestTool {
public static void main(String[] args) {
// TODO Auto-generated method stub
NoBug testobj = new NoBug();
Class clazz = testobj.getClass();
Method[] method = clazz.getDeclaredMethods();
//用來記錄測試產生的 log 資訊
StringBuilder log = new StringBuilder();
// 記錄異常的次數
int errornum = 0;
for ( Method m: method ) {
// 只有被 @Jiecha 標註過的方法才進行測試
if ( m.isAnnotationPresent( Jiecha.class )) {
try {
m.setAccessible(true);
m.invoke(testobj, null);
} catch (Exception e) {
// TODO Auto-generated catch block
//e.printStackTrace();
errornum++;
log.append(m.getName());
log.append(" ");
log.append("has error:");
log.append("\n\r caused by ");
//記錄測試過程中,發生的異常的名稱
log.append(e.getCause().getClass().getSimpleName());
log.append("\n\r");
//記錄測試過程中,發生的異常的具體資訊
log.append(e.getCause().getMessage());
log.append("\n\r");
}
}
}
log.append(clazz.getSimpleName());
log.append(" has ");
log.append(errornum);
log.append(" error.");
// 生成測試報告
System.out.println(log.toString());
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
6.2.4 執行結果
3 x 5=15
1-1=0
1+1=11
1234567890
我寫的程式沒有 bug!
chufa has error:
caused by ArithmeticException
/ by zero
NoBug has 1 error.
1
2
3
4
5
6
7
8
9
提示 NoBug 類中的 chufa() 這個方法有異常,這個異常名稱叫做 ArithmeticException,原因是運算過程中進行了除 0 的操作。

所以,我們透過測試得出結論,NoBug 這個類有 Bug。

這樣,透過註解我完成了我自己的目的,那就是對別人的程式碼進行測試。

所以,再問我註解什麼時候用?我只能告訴你,這取決於你想利用它幹什麼用。

0x07 註解應用例項
註解運用的地方太多了,如:
JUnit 這個是一個測試框架,典型使用方法如下:

public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
1
2
3
4
5
6
@Test 標記了要進行測試的方法 addition_isCorrect().

還有例如ssm框架等運用了大量的註解。

0x08 總結
如果註解難於理解,你就把它類同於標籤,標籤為了解釋事物,註解為了解釋程式碼
註解的基本語法,建立如同介面,但是多了個 @ 符號
註解的元註解
註解的屬性
註解主要給編譯器及工具型別的軟體用的
註解的提取需要藉助於 Java 的反射技術,反射比較慢,所以註解使用時也需要謹慎計較時間成本
註解生效週期:在javac執行的註解處理階段會對註解進行分析,根據註解的作用將其還原成具體的指令集。
JDK1.6之前註解是在執行期起作用,JDK1.6提供了插入式註解處理器在編譯期處理註解,相當於編譯器外掛,會按需修改抽象語法樹,所以一旦修改了AST,會回到第一步重複詞法語法分析生成抽象語法樹(AST)、生成符號表、註解處理三步,直到註解處理不再修改AST。