Java註解知識梳理與簡單使用

大棋發表於2019-05-15

註解

         註解是什麼?簡單說註解就是一種標註(標記、標識),沒有具體的功能邏輯程式碼。也可以把註解理解為程式碼裡的特殊標記,這些標記可以在編譯,類載入,執行時被讀取,並執行相應的處理。通過註解開發人員可以在不改變原有程式碼和邏輯的情況下在原始碼中嵌入補充資訊。

預定義的註解型別

JDK 中內建了以下註解:

  • @Override
  • @Deprecated
  • @SuppressWarnnings
  • @SafeVarargs(JDK7)
  • @FunctionalInterface(JDK8)

@Override

    @Override 旨在通知編譯器該方法是覆蓋父類中宣告的方法

通過IDE快捷鍵實現介面方法和複寫父類方法時,都會自動新增@Override註解。

@Deprecated

    @Deprecated 標記已棄用的元素,不應再使用。將方法,類或欄位標記為@Deprecated註解時,當使用者使用該方法,類或欄位時,編譯器就會生成警告。

Java註解知識梳理與簡單使用

Java註解知識梳理與簡單使用
    將方法,類或欄位標記為為@Deprecated註解時,編譯器都會將被標記@Deprecated註解的方法,類或欄位被快捷使用時,用刪除線進行修飾。

@SuppressWarnnings

    @SuppressWarnnings 關閉不當的編譯器警告資訊。

    Java語言規範列出了兩個類別:deprecation 和 unchecked。"unchecked"用於抑制未經檢查的警告。"deprecation" 使用了不推薦的類或方法的警告。

@SafeVarargs

    @SafeVarargs註解應用於方法或建構函式時,斷言程式碼不對其varargs引數執行可能不安全的操作。

    @SafeVarargs註解只能用在引數長度可變的方法或構造方法上,且方法必須宣告為static或final,否則會出現編譯錯誤。

    從JVM物件的角度來看…與Object []幾乎一樣。

Java註解知識梳理與簡單使用
Java註解知識梳理與簡單使用

@FunctionalInterface

    @FunctionalInterface 宣告介面是函式式介面。

    你用@FunctionalInterface定義了一個介面,而它卻不是函式式介面的話,編譯器將返回一個提示原因的錯誤。

    什麼是函式式介面?函式式介面就是隻定義一個抽象方法的介面,但是可以有多個預設方法或靜態方法的介面。

    Java 8允許在介面內宣告靜態方法預設方法。預設方法是指提供介面方法的預設實現,用default關鍵字進行修飾。

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
複製程式碼

    Java8的謂詞介面Predicate,其本身的除了唯一的抽象方法外,還定義了預設方法和靜態方法。

元註解

適用於其他註解的註解稱為元註解。在java.lang.annotation中定義了幾種元註解型別。

  • @Retention
  • @Documented
  • @Target
  • @Inherited
  • @Repeatable(JDK8)

@Retention

    @Retention 宣告註解的保留策略。

檢視Retention的原始碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
複製程式碼

    由原始碼得知,Retention的值是一個RetentionPolicy型別的變數,而RetentionPolicy是一個列舉值,其值包括:

  • RetentionPolicy.SOURCE    標記的註解僅保留在源級別中,編譯器將丟棄該註解。
  • RetentionPolicy.CLASS    註解將由編譯器記錄在class檔案中 但在執行時不需要由JVM保留。
  • RetentionPolicy.RUNTIME    註解將由編譯器記錄在class檔案中,並在執行時由JVM保留,因此可以反射性地讀取它們。

    如果註解型別宣告中不存在Retention註解,則Retention預設為 RetentionPolicy.CLASS

@Documented

    無論何時使用指定的註解,都應使用Javadoc工具記錄這些元素。 如果使用Documented註解型別宣告,則其註解將成為帶註解元素的公共API的一部分。

@Target

     @Target註解用於限制可以應用該註解的Java元素型別。

檢視Target的原始碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
複製程式碼

    由原始碼得知,Target的值是一個ElementType型別的陣列變數,即可以同時設定多個值。而ElementType是一個列舉值,其值包括: Target註解指定以下元素型別之一作為其值:

  • ElementType.TYPE    應用於類,介面(包括註解型別)或列舉宣告。
  • ElementType.FIELD    應用欄位宣告(包括列舉常量)。
  • ElementType.METHOD    應用於方法宣告。
  • ElementType.PARAMETER    應用於正式引數宣告。
  • ElementType.CONSTRUCTOR    應用於建構函式宣告。
  • ElementType.LOCAL_VARIABLE    應用於區域性變數宣告。
  • ElementType.ANNOTATION_TYPE    應用於註解型別宣告。
  • ElementType.PACKAGE    應用於包宣告。
  • ElementType.TYPE_PARAMETER    應用於型別變數的宣告語句前。(JDK8)
  • ElementType.TYPE_USE    應用於所有使用型別的任何語句(如:泛型,型別轉換等)(JDK8)

    ElementType.TYPE_PARAMETER 和 ElementType.TYPE_USE屬於Java 8的新特性,具體看下面Java8 註解新特性。

@Inherited

     @Inherited註解表明註解型別可以從超類繼承。當使用者查詢註解型別並且該類沒有此型別的註解時,將查詢類的超類以獲取註解型別。將重複此過程,直到找到此型別的註解,或者到達類層次結構(物件)的頂部。如果沒有超類具有此型別的註解,則查詢將指示相關類沒有此類註解。此註解僅適用於類宣告。

Java8 註解新特性

Java 8在兩個方面對註解機制進行了改進,分別為:

  • 可以定義重複註解
  • 可以為任何型別新增註解

型別註解(Type Annotation)

    在Java 8之前,只有宣告可以被註解。Java 8中,註解可以寫在使用型別的任何地方,例如括new操作符、型別轉換、instanceof檢查、泛型型別引數,以及implements和throws子句。例如:

//列表泛型
List<@NonNull Car> cars = new ArrayList<>(); 
//物件型別轉化時
myString = (@NonNull String) str;
//使用 implements 表示式時
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
 }
 //使用 throws 表示式時
 public void validateValues() throws @Critical ValidationFailedException{
  }
複製程式碼

    定義一個型別註解(Type Annotation)的方法與普通的 Annotation 類似,只需要指定 Target 為 ElementType.TYPE_PARAMETER 或者 ElementType.TYPE_USE,或者同時指定這兩個 Target。

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public  @interface daqiAnnotation {
}
複製程式碼
  • ElementType.TYPE_PARAMETER 表示這個註解可以用在型別變數的宣告語句前。

  • ElementType.TYPE_USE 表示這個註解可以用在所有使用型別的任何語句中(如:泛型,型別轉換等)

    Java 8 通過引入型別註解(Type Annotation),使得開發者可以在更多的地方使用 Annotation,從而能夠更全面地對程式碼進行分析以及進行更強的型別檢查。

重複註解(Repeating Annotation)

    Java8 之前禁止對同樣的註解型別宣告多次。在實際應用中,可能會出現需要對同一個宣告式或者型別加上相同的 Annotation(包含不同的屬性值)的情況。

@interface Author { String name(); }

@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")
class Book{ }
複製程式碼

可以宣告一個新的註解,其包含希望重複的註解陣列。

@interface Author { String name(); }

@interface Authors {
 Author[] value();
}

@Authors({ @Author(name="Raoul"), @Author(name="Mario") ,@Author(name="Alan")})
class Book{} 
複製程式碼

Java8 之後,當一個註解在設計之初就是可重複的,可以通過兩種途徑實現:

  • 將註解標記為@Repeatable
  • 提供一個註解的容器(即Java8之前的實現方式)

@Repeatable示例:

@Repeatable(Authors.class)
@interface Author { String name(); } 

@interface Authors {
 Author[] value();
} 

@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")
class Book{ } 
複製程式碼

        編譯時,Book類會被認為使用了@Authors({@Author(name="Raoul"), @Author(name =”Mario”), @Author(name=”Alan”)})的形式進行註解。所以,可以把重複註解(Repeating Annotation)看成是一種語法糖,它提供了Java程式設計師之前慣用的功能。

        由於相容性的緣故,重複註解(Repeating Annotation)並不是所有新定義的 Annotation 的預設特性,需要開發者根據自己的需求決定新定義的 Annotation 是否可以重複標註。

自定義註解

以android最為熟悉的findVIewById 和 onClick為例,定義兩個執行時儲存的註解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface findViewById {
    int value();
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface onClickById {
    int value();
}
複製程式碼

        由於onClickById 和 findViewById 的元註解Retention的值為RetentionPolicy.RUNTIME,則表示在程式執行時,可以獲取到該註解。

        通過註解中攜帶的value,對屬性或方法進行反射,從而實現屬性初始化和點選事件繫結的目的。

public class daqiAnnotationUtils {

    public static void inject(Activity activity) {
        injectFiled(activity);
        injectEvent(activity);
    }

    private static void injectFiled(Activity activity){
        //獲取Activity的所有屬性
        Field[] fields = activity.getClass().getDeclaredFields();
        //尋找有findViewById註解的屬性
        for (Field field : fields) {
            findViewById viewById =  field.getAnnotation(findViewById.class);
            if(viewById != null){
                //通過findViewById註解中的值,通過activity#findViewById找到對應的View
                View view = activity.findViewById(viewById.value());
                //設定可以反射私有變數
                field.setAccessible(true);
                try {
                    //將獲取到的view賦值到對應的變數中。
                    field.set(activity,view);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void injectEvent(final Activity activity){
        //獲取Activity的所有方法
        Method[] methods = activity.getClass().getDeclaredMethods();
        //尋找有onClickById註解的屬性
        for (Method method : methods) {
            onClickById clickById = method.getAnnotation(onClickById.class);
            if (clickById != null){
                //通過onClickById註解中的值,通過activity#findViewById找到對應的View
                final View view = activity.findViewById(clickById.value());
                if (view != null) {
                    final Method mMethod = method;
                    //設定View#setOnClickListener
                    view.setOnClickListener(new View.OnClickListener(){
                        @Override
                        public void onClick(View v) {
                            try {
                                mMethod.setAccessible(true);
                                //反射執行方法
                                mMethod.invoke(activity);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }
    }
}
複製程式碼

        定義兩個個TextView,一個用作展示文字,對應的id為R.id.nameText;一個用作點選按鈕,對應id為R.id.textBtn。

        通過daqiAnnotationUtils#inject(Activity)初始化activity中有findViewById註解的變數,並將有onClickById的方法與其對應的元件實現點選監聽的繫結。

public class daqiActivity extends FragmentActivity {

    @findViewById(R.id.nameText)
    private TextView mName;

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

        //初始化註解的變數和方法
        daqiAnnotationUtils.inject(this);

        //修改名稱
        mName.setText("daqi");
    }

    @onClickById(R.id.textBtn)
    public void toastName(){
        Toast.makeText(daqiActivity.this,
                "daqi",Toast.LENGTH_SHORT).show();
    }
}
複製程式碼

實現效果

Java註解知識梳理與簡單使用

參考文件:

甲骨文-Java註解文件

IBM-Java 8 Annotation 新特性

《 Java8 實戰 》

相關文章