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

註解

         註解是什麼?簡單說註解就是一種標註(標記、標識),沒有具體的功能邏輯代碼。也能夠把註解理解爲代碼裏的特殊標記,這些標記能夠在編譯,類加載,運行時被讀取,並執行相應的處理。經過註解開發人員能夠在不改變原有代碼和邏輯的狀況下在源代碼中嵌入補充信息。html

預約義的註解類型

JDK 中內置瞭如下註解:java

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

@Override

    @Override 旨在通知編譯器該方法是覆蓋父類中聲明的方法android

經過IDE快捷鍵實現接口方法和複寫父類方法時,都會自動添加@Override註解。程序員

@Deprecated

    @Deprecated 標記已棄用的元素,不該再使用。將方法,類或字段標記爲@Deprecated註解時,當用戶使用該方法,類或字段時,編譯器就會生成警告。數組

    將方法,類或字段標記爲爲@Deprecated註解時,編譯器都會將被標記@Deprecated註解的方法,類或字段被快捷使用時,用刪除線進行修飾。

@SuppressWarnnings

    @SuppressWarnnings 關閉不當的編譯器警告信息。安全

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

@SafeVarargs

    @SafeVarargs註解應用於方法或構造函數時,斷言代碼不對其varargs參數執行可能不安全的操做。oracle

    @SafeVarargs註解只能用在參數長度可變的方法或構造方法上,且方法必須聲明爲static或final,不然會出現編譯錯誤。ide

    從JVM對象的角度來看…與Object []幾乎同樣。函數

@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註解文檔

IBM-Java 8 Annotation 新特性

《 Java8 實戰 》

相關文章
相關標籤/搜索