Java 註解(Annotation):帶你一步步探索神祕的註解(Annotation)

前言

  • Java中,註解(Annotation)很是重要,但對於不少開發者來講,這並不容易理解,甚至以爲有點神祕
  • 今天,我將獻上一份 Java註解的介紹 & 實戰攻略,但願大家會喜歡。

目錄

示意圖


1. 簡介

  • 定義:一種標識 / 標籤
    1. 註解屬於 Java中的一種類型,同 類class 和 接口interface 同樣
    2. Java SE 5.0開始引入
    3. 基礎做用:標識 / 解釋 Java 代碼


2. 應用場景

  • 面向 編譯器 / APT 使用php

    1. APT(Annotation Processing Tool):提取 & 處理 Annotation 的代碼
    2. 由於當開發者使用Annotation 修飾類、方法、方法 等成員後,這些 Annotation 不會本身生效,必須由開發者提供相應的代碼來提取並處理 Annotation 信息。這些處理提取和處理 Annotation 的代碼統稱爲 APT
  • 因此說,註解除了最基本的解釋 & 說明代碼,註解的應用場景還取決於你想如何利用它java

  • 下面我舉幾個典型的應用場景:

場景1:測試代碼

如出名的測試框架JUnit = 採用註解進行代碼測試git

public class ExampleUnitTest {
    @Test
    public void Method() throws Exception {
          ...
    }
}
// @Test 標記了要進行測試的方法Method() 複製代碼

場景2:簡化使用 & 下降代碼量

Http網絡請求庫 Retrofit & IOC 框架ButterKnifegithub

<-- Http網絡請求庫 Retrofit -->

// 採用 註解 描述 網絡請求參數
public interface GetRequest_Interface {

 @GET("ajax.php?a=fy&f=auto&t=auto&w=hello%20world")
    Call<Translation> getCall();

 }

<-- IOC 框架ButterKnife -->
public class MainActivity extends AppCompatActivity {

// 經過註解設置資源
@BindView(R.id.test)
    TextView mTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
    }
}複製代碼

3. 註解的類型

註解的類型包括:元註解 、 Java內置註解 & 自定義註解。ajax

3.1 元註解

3.1.1 簡介
  • 定義:一種標識 / 標籤
    是一種基本註解 = Android系統內置的註解
  • 做用:標識 / 解釋 開發者自定義的註解
3.1.2 註解、元註解 & Java代碼的關係
  • 元註解做用於註解 & 解釋註解
// 元註解 做用於註解 & 解釋註解
// 元註解在註解定義時進行定義
@元註解
public @interface Carson_Annotation {

}複製代碼
  • 註解做用於Java代碼 & 解釋Java代碼
// 註解做用於Java代碼 & 解釋Java代碼
@Carson_Annotation
public class Carson {
}
複製代碼
3.1.3 元註解類型介紹
  • 元註解共有5種,包括:

示意圖

  • 下面我將一一介紹

@Retention

  • 定義:保留註解
  • 做用:解釋 / 說明了註解的生命週期
  • 具體使用
// 元註解@Retention(RetentionPolicy.RUNTIME)的做用:說明 註解Carson_Annotation的生命週期 = 保留到程序運行時 & 被加載到 JVM 中
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation {
}

<-- 元註解@Retention參數說明 -->
// RetentionPolicy.RUNTIME:註解保留到程序運行時 & 會被加載進入到 JVM 中,因此在程序運行時能夠獲取到它們
// RetentionPolicy.CLASS:註解只被保留到編譯進行時 & 不會被加載到 JVM 
// RetentionPolicy.SOURCE:註解只在源碼階段保留 & 在編譯器進行編譯時將被丟棄忽視。
複製代碼

@Documented

  • 定義:Java文檔註解
  • 做用:將註解中的元素包含到 Javadoc文檔中
  • 具體使用
// 元註解@Documented做用:說明 註解Carson_Annotation的元素包含到 Javadoc 文檔中
@Documented
public @interface Carson_Annotation {
}複製代碼

@Target

  • 定義:目標註解
  • 做用:限定了註解做用的目標範圍,包括類、方法等等
  • 具體使用
// 元註解@Target做用:限定了註解Carson_Annotation做用的目標範圍 = 方法
// 即註解Carson_Annotation只能用於解釋說明 某個方法
@Target(ElementType.METHOD)
public @interface Carson_Annotation {
}

<-- @Target取值參數說明 -->
// ElementType.PACKAGE:能夠給一個包進行註解
// ElementType.ANNOTATION_TYPE:能夠給一個註解進行註解
// ElementType.TYPE:能夠給一個類型進行註解,如類、接口、枚舉
// ElementType.CONSTRUCTOR:能夠給構造方法進行註解
// ElementType.METHOD:能夠給方法進行註解
// ElementType.PARAMETER 能夠給一個方法內的參數進行註解
// ElementType.FIELD:能夠給屬性進行註解
// ElementType.LOCAL_VARIABLE:能夠給局部變量進行註解

複製代碼

@Inherited

  • 定義:繼承註解
  • 做用:使得一個 被@Inherited註解的註解 做用的類的子類能夠繼承該類的註解
    1. 前提:子類沒有被任何註解應用
    2. 如,註解@Carson_Annotation(被元註解@Inherited 註解)做用於A類,那麼A類的子類則繼承了A類的註解
    3. 具體使用

// 元註解@Inherited 做用於 註解Carson_Annotation
@Inherited
public @interface Carson_Annotation {
}


// 註解Carson_Annotation 做用於A類
@Carson_Annotation
public class A {
  }

// B類繼承了A類,即B類 = A類的子類,且B類沒被任何註解應用
// 那麼B類繼承了A類的註解 Carson_Annotation
public class B extends A {}複製代碼

@Repeatable

  • 定義:可重複註解
    Java 1.8後引進
  • 做用:使得做用的註解能夠取多個值
  • 具體使用
// 1. 定義 容器註解 @ 職業
public @interface Job {
    Person[]  value();
}
<-- 容器註解介紹 -->
// 定義:自己也是一個註解
// 做用:存放其它註解
// 具體使用:必須有一個 value 屬性;類型 = 被 @Repeatable 註解的註解數組
// 如本例中,被 @Repeatable 做用 = @Person ,因此value屬性 = Person []數組

// 2. 定義@Person 
// 3. 使用@Repeatable 註解 @Person
// 注:@Repeatable 括號中的類 = 容器註解
@Repeatable(Job.class)
public @interface Person{
    String role default "";
}

// 在使用@Person(被@Repeatable 註解 )時,能夠取多個值來解釋Java代碼
// 下面註解表示:Carson類便是產品經理,又是程序猿
@Person(role="coder")
@Person(role="PM")
public class Carson{

}

複製代碼
注:一個註解能夠被多個元註解做用
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation {
}複製代碼
3.1.4 元註解類型總結
![示意圖](http://upload-images.jianshu.io/upload_images/944365-6df4776e4dfcc865.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

3.2 Java內置的註解

  • 定義:即Java內部已經實現好的註解
  • 類型:Java中 內置的註解有5類,具體包括:
    示意圖數組

  • 下面我將對這5類內置註解進行講解安全

@Deprecated

  • 定義:過期註解
  • 做用:標記已過期 & 被拋棄的元素(類、方法等)
  • 具體使用
// 用 註解@Deprecated 標記類中已過期的 方法Hello()
public class Buyer2   {

    @Deprecated
    public void Hello(){
        System.out.println("Hello 2015!");
    }
}複製代碼

使用該類中被 @Deprecated 做用的方法時,IDE會提示該方法已過期 / 拋棄markdown

示意圖

@Override

  • 定義:複寫註解
  • 做用:標記該方法須要被子類複寫
  • 具體使用:該方法你們很熟悉了,此處不做過多描述

@SuppressWarnings

  • 定義:阻止警告註解
  • 做用:標記的元素會阻止編譯器發出警告提醒
    主要應用於開發者須要忽略警告時
  • 具體使用
// 在括號內傳入須要忽略警告的屬性
@SuppressWarnings("deprecation")
public void test(){
    Buyer2 mBuyer2 = new Buyer2();
    mBuyer2.hello();
    // IDE將不會發出警告(即不會在hello()出現劃線)
}複製代碼

@SafeVarargs

  • 定義:參數安全類型註解
    Java 1.7 後引入
  • 做用:提醒開發者不要用參數作不安全的操做 & 阻止編譯器產生 unchecked警告
  • 具體使用
// 如下是官方例子
// 雖然編譯階段不報錯,但運行時會拋出 ClassCastException 異常
// 因此該註解只是做提示做用,可是實際上仍是要開發者本身處理問題
@SafeVarargs // Not actually safe!
    static void m(List<String>... stringLists) {
    Object[] array = stringLists;
    List<Integer> tmpList = Arrays.asList(42);
    array[0] = tmpList; // Semantically invalid, but compiles without warnings
    String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}複製代碼

@FunctionalInterface

  • 定義:函數式接口註解網絡

    Java 1.8 後引入的新特性多線程

  • 做用:表示該接口 = 函數式接口

    函數式接口 (Functional Interface) = 1個具備1個方法的普通接口

  • 具體使用

// 多線程開發中經常使用的 Runnable 就是一個典型的函數式接口(被 @FunctionalInterface 註解)
@FunctionalInterface
public interface Runnable {

    public abstract void run();
}

<--額外:爲何要用函數式接口標記 -->
// 緣由:函數式接口很容易轉換爲 Lambda 表達式
// 這是另一個很大話題,此處不做過多講解,感興趣的同窗可自行了解複製代碼

Java內置註解類型總結

示意圖


3.3 自定義註解

  • 定義:開發者自定義註解
  • 具體使用:下面第4節將這種介紹。

4. 使用介紹

註解的基礎使用包括定義、屬性 & 具體使用

4.1 註解的定義

// 經過 @interface 關鍵字進行定義
// 形式相似於接口,區別在於多了一個 @ 符號

public @interface Carson_Annotation {

}

// 上面的代碼建立了一個名爲 Carson_Annotaion 的註解複製代碼

4.2 註解的屬性

<-- 1. 定義 註解的屬性 -->
// 註解的屬性 在定義該註解自己時 進行定義
public @interface Carson_Annotation {
// 註解的屬性 = 成員變量
// 註解只有成員變量,沒有方法

    // 註解@Carson_Annotation中有2個屬性:id 和 msg  
    int id();
    String msg() default "Hi" ;

    // 說明:
      // 註解的屬性以 「無形參的方法」 形式來聲明
      // 方法名 = 屬性名
      // 方法返回值 = 屬性類型 = 8 種基本數據類型 + 類、接口、註解及對應數組類型
      // 用 default 關鍵值指定 屬性的默認值,如上面的msg的默認值 = 」Hi「
}

<-- 2. 賦值 註解的屬性 -->
// 註解的屬性在使用時進行賦值
// 註解屬性的賦值方式 = 註解括號內以 「value=」xx」 「 形式;用 」,「隔開多個屬性

// 註解Carson_Annotation 做用於A類
// 在做用 / 使用時對註解屬性進行賦值
@Carson_Annotation(id=1,msg="hello,i am Carson")
public class A {
  }

// 特別說明:若註解只有一個屬性,則賦值時」value「能夠省略

// 假設註解@Carson_Annotation只有1個msg屬性
// 賦值時直接賦值便可
@Carson_Annotation("hello,i am Carson")
public class A {
  }
複製代碼

4.3 註解的應用

// 在類 / 成員變量 / 方法 定義前 加上 「@註解名」 就可使用該註解
@Carson_Annotation
public class Carson {

    @Carson_Annotation
    int a;

    @Carson_Annotation
    public void bMethod(){}

}

// 即註解@Carson_Annotation 用於標識解釋 Carson類 / a變量 / b方法複製代碼

4.4 獲取註解

  • 定義:即獲取某個對象上的全部註解。
  • 實現手段:Java 反射技術
    因爲反射須要較高時間成本,因此註解使用時要謹慎考慮時間成本
  • 具體使用
<-- 步驟1:判斷該類是否應用了某個註解 -->
// 手段:採用 Class.isAnnotationPresent() 
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

<-- 步驟2:獲取 註解對象(Annotation)-->
  // 手段1:採用getAnnotation()  ;返回指定類型的註解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
  // 手段2:採用getAnnotations() ;返回該元素上的全部註解
public Annotation[] getAnnotations() {}
複製代碼
  • 使用實例
    下面我將用一個例子展現如何獲取一個類、方法 & 成員變量上的註解類型

步驟1:自定義2個註解

Carson_Annotation.java

// 由於註解@Carson_Annotation須要在程序運行時使用
// 因此必須採用元註解Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation {

    // 註解@Carson_Annotation中有2個屬性:id 和 msg
    int id();
    String msg() default "Hi";
}複製代碼

Carson_Annotation2.java

// 由於註解@Carson_Annotation2須要在程序運行時使用
// 因此必須採用元註解Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation2 {

    // 註解@Carson_Annotation2中有2個屬性:id 和 msg
    int id();
    String msg() default "Hi";
}複製代碼

步驟2:定義1個被註解的類

Test.java

// 1個註解做用於Test類
@Carson_Annotation(id = 1,msg="我是類Test")
public class Test {

    // 1個註解 做用於Test類成員變量a
    @Carson_Annotation(id = 2,msg="我是變量a")
    int a;

    // 2個註解 做用於Test類方法
    @Carson_Annotation(id = 3,msg="我是方法b")
    @Carson_Annotation2(id = 4,msg="我是方法bb(來自第2個註解)")
    public void bMethod(){}

    }複製代碼

步驟3:分別獲取一個類、方法 & 成員變量上的註解

private static final String TAG = "Annotation";

        /**
         * 講解1:獲取類上的註解
         */

        // 1. 判斷Test類是否應用了註解@Carson_Annotation
        boolean hasAnnotation = Test.class.isAnnotationPresent(Carson_Annotation.class);

        // 2. 若是應用了註解 = hasAnnotation = true
        //    則獲取類上的註解對象
        if ( hasAnnotation ) {
            Carson_Annotation classAnnotation = Test.class.getAnnotation(Carson_Annotation.class);

            // 3. 獲取註解對象的值
            Log.d(TAG, "我是Test類上的註解");
            Log.d(TAG, "id:" + classAnnotation.id());
            Log.d(TAG, "msg:" + classAnnotation.msg());
        }

        /**
         * 講解2:獲取類成員變量a上的註解
         */
        try {
            // 1. 獲取類上的成員變量a
            Field a = Test.class.getDeclaredField("a");
            a.setAccessible(true);

            // 2. 獲取成員變量a上的註解@Carson_Annotation
            Carson_Annotation variableAnnotation = a.getAnnotation(Carson_Annotation.class);

            // 3. 若成員變量應用了註解 = hasAnnotation = true
            //    則獲取註解對象上的值 = id & msg
            if ( variableAnnotation != null ) {
                Log.d(TAG, "我是類成員變量a上的註解");
                Log.d(TAG, "id:" + variableAnnotation.id());
                Log.d(TAG, "msg:" + variableAnnotation.msg());

            }

            /**
             * 講解3:獲取類方法bMethod上的註解
             */

            // 1. 獲取類方法bMethod
            Method testMethod = Test.class.getDeclaredMethod("bMethod");


            // 2. 獲取方法上的註解
            if ( testMethod != null ) {
                // 由於方法上有2個註解,因此採用getAnnotations()得到全部類型的註解
                Annotation[] ans = testMethod.getAnnotations();

                Log.d(TAG, "我是類方法bMethod的註解");

                // 3. 獲取註解的值(經過循環)
                for( int i = 0;i < ans.length  ;i++) {


                    Log.d(TAG, "類方法bMethod的" + "註解"+ i+ 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());
        }
    }複製代碼
  • 測試結果

示意圖

  • Demo地址

Carson的Github地址:Java_Annotation


5. 實例演練

下面,我將經過註解實現一個最多見的應用場景:測試代碼

5.1 實例說明

經過註解,檢查一個類中的方法是否存在異常

5.2 具體步驟

  • 步驟1:自定義測試註解

Carson_Test.java

// 由於註解@Carson_Test須要在程序運行時使用
// 因此必須採用元註解Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Test {

}複製代碼
  • 步驟2:定義須要測試的類

Test.java

public class Test {

    @Carson_Test
    public void method1(){
        System.out.println("method1正常運行 = " + (1+1));
    }
    @Carson_Test
    public void method2(){
        System.out.println("method2正常運行 = " + (2*3));
    }

    @Carson_Test
    public void method3(){
        System.out.println("method3正常運行 = " + (2/0));
    }

}複製代碼
  • 步驟3:採用註解 測試 類的方法 是否存在 Bug

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 1. 獲取測試類Test對象
        Test mTest = new Test();
        Class mTest_Class = mTest.getClass();

        // 2. 獲取測試類Test的全部方法(經過數組存放)
        Method[] method = mTest_Class.getDeclaredMethods();

        // 3. 經過註解@Carson_Test 測試類中的方法

        // a. 遍歷類中全部方法
        for ( Method m: method ) {
            // b. 只有被 @Carson_Test 標註過的方法才容許進行測試
            if ( m.isAnnotationPresent( Carson_Test.class )) {
                try {
                    m.setAccessible(true);
                    // c. 經過反射調用測試類中的方法
                    m.invoke(mTest);
                    // d. 捕捉方法出現的異常 & 輸出異常信息
                } catch (Exception e) {
                    System.out.println( "Test類出現Bug了!!!");
                    System.out.println( "出現Bug的方法:" + m.getName());
                    System.out.println( "Bug類型:" + e.getCause().getClass().getSimpleName());
                    System.out.println( "Bug信息:" + e.getCause().getMessage());
                }
            }
        }
}複製代碼

5.3 測試結果

image.png

5.4 Demo地址

Carson_Ho的Github地址:Annation_Debug


6. 總結

  • 本文全面講解了Java註解(Annotation)的相關知識,相信您對Java註解已經瞭解深入。

  • 下面我將繼續對 Android中的知識進行深刻講解 ,有興趣能夠繼續關注Carson_Ho的安卓開發筆記


請幫頂 / 評論點贊!由於你的鼓勵是我寫做的最大動力!

相關文章
相關標籤/搜索