Java高級特性——註解,這也許是最簡單易懂的文章了

博主在初學註解的時候看到網上的介紹大部分都是直接介紹用法或者功能,沒有實際的應用場景,篇幅又很長導致學習的時候難以理解其意圖,而且學完就忘QAQ。本篇文章中我將結合實際的應用場景儘可能由淺入深,平緩的介紹java註解。

java註解是jdk1.5以後新出的特性,對於它的應用非常廣泛,我們首先來看一下註解的應用,百度百科上這樣說:

註解的作用.PNG

我們可以看到,註解的作用有三方面:

編寫doc文檔:這個就我們很常用的 @return 以及 @author,加了這些註解以後,就可以用jdk幫我們自動生成對應的API文檔了

編譯檢查:這個也很常見 @Override,而且功能很強大,我將會在以後的文章中介紹

進行代碼分析:這是本篇文章的重點。這個和編譯檢查一樣也是一個強大的功能,但相比與編譯檢查由於其用到了反射,在性能上存在一些問題

後臺開發中的SSH三大框架,以及咱們安卓端的retrofit,ButterKnife,Lombok等框架和插件也是大量的用到了註解。這裏我將通過手擼一個假的ButterKnife來具體演示註解有什麼用,怎麼用。

我們首先來看段代碼

public class MainActivity extends AppCompatActivity {

@OnClick(R.id.test_btn)
void test(){
    test_tv.setText("恭喜您,綁定成功了!");
}

@FindViewByID(R.id.test_tv)
TextView test_tv;

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

這是一個最基本的activity,裏面有2個控件,Button和TextView

device-2017-08-03-145450.png

在我點擊Button後TextView的文字被改變。而我所做的僅僅是ButterKnife.bindView(this)並添加2個註解而已,這樣就實現了控件的綁定,省去了很多與業務無關的代碼,是不是簡潔了很多。

看了註解的功能是不是很想了解它是怎麼做到的,接下來我就來看看它是什麼,怎麼用,怎麼利用

什麼是註解

官方把它叫做元數據,即一種描述數據的數據。所以,可以說註解就是源代碼的元數據。用它來可以來描述、標記我們的源代碼。

怎樣定義一個註解

以下是我上文中定義的一個 @OnClick註解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {

int value() default 0;

}
可見和定義一個類一樣,只是將class改爲 @interface,並且頂部通過幾個原註解來說明這個註解的一些重要信息,具體如下:

J2SE5.0版本在 java.lang.annotation提供了四種元註解,專門註解其他的註解:

@Documented –註解是否將包含在JavaDoc中
@Retention –什麼時候使用該註解
@Target? –註解用於什麼地方
@Inherited – 是否允許子類繼承該註解

@Documented–一個簡單的Annotations標記註解,表示是否將註解信息添加在java文檔中,一般不用管。

@Retention– 定義該註解的生命週期,很重要,必須指定,以下是3種生命週期的介紹

RetentionPolicy.SOURCE – 在編譯階段丟棄。這些註解在編譯結束之後就不再
有任何意義,所以它們不會寫入字節碼。@Override, @SuppressWarnings都屬
於這類註解。
RetentionPolicy.CLASS – 在類加載的時候丟棄。在字節碼文件的處理中有用。
註解默認使用這種方式。
RetentionPolicy.RUNTIME– 始終不會丟棄,運行期也保留該註解,因此可以使
用反射機制讀取該註解的信息。我們自定義的註解通常使用這種方式。

@Target – 表示該註解用於什麼地方。如果不明確指出,該註解可以放在任何地方。以下是一些可用的參數。需要說明的是:屬性的註解是兼容的,如果你想給7個屬性都添加註解,僅僅排除一個屬性,那麼你需要在定義target包含所有的屬性。

ElementType.TYPE:用於描述類、接口或enum聲明
ElementType.FIELD:用於描述實例變量
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE 另一個註釋
ElementType.PACKAGE 用於記錄java文件的package信息

@Inherited – 定義該註釋和子類的關係

那麼註解體裏的內容有該怎樣定義

Annotations只支持基本類型、String及枚舉類型。註釋中所有的屬性被定義成方法,並允許提供默認值。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Book{
public enum Priority {LOW, MEDIUM, HIGH}
String author() default "Yash";
int price() default 20;
Status status() default Status.NOT_STARTED;
}

看看怎麼用它

@Todo(priority = Todo.Priority.MEDIUM, author = "zsq", status = 
Todo.Status.STARTED)
public void incompleteMethod1() {

}

通過字段名 = 的形式給字段賦值,如果沒賦值,則使用缺省值。如果註解中只有一個屬性,可以直接命名爲“value”,使用時無需再標明屬性名,例如我定義的 @OnClick註解。

好了,花了那麼多精力來認識他,該看看該怎麼來利用它了

我們定義了自己的註解並將其應用在業務邏輯的方法上。現在我們需要寫一個用戶程序調用我們的註解。這裏我們需要使用反射機制。如果你熟悉反射代碼,就會知道反射可以提供類名、方法和實例變量對象。所有這些對象都有getAnnotation()這個方法用來返回註解信息。我們需要把這個對象轉換爲我們自定義的註釋(使用 instanceOf()檢查之後),同時也可以調用自定義註釋裏面的方法。

所有這些對象都有getAnnotation()!
所有這些對象都有getAnnotation()!
所有這些對象都有getAnnotation()!

重要的API說3遍,另外用到的幾個方法也很重要,下面的代碼會演示,更多的API使用參考可以去查閱JDK文檔。

具體到我們本編文章的實例,調用註解的傢伙就是我們剛剛在MainActivity裏用到的 ButterKnife,我們通過設置監聽的註解來看看它到底做了什麼

public static final void bindView(final Activity activity){
    traversalMethod(activity);
    traversalField(activity);
}

在我們調用的ButterKnife.bindView(this)中我們拿到了MainActivity的實例,並且通過反射遍歷裏面所有的方法:

private static void traversalMethod(final Activity activity) {
    Method[] methodArray = getObjectMethodArray(activity);
    for (final Method method:methodArray){
        if(isAnnotationPresent(method)){

            int viewID = getViewID(method);

            setOnClickListenerForControl(activity, method, viewID);

        }
    }
}

private static Method[] getObjectMethodArray(Activity activity) {
    return activity.getClass().getMethods();
}

接着判斷方法是否被我們註解:

private static boolean isAnnotationPresent(Method method) {
    return method.isAnnotationPresent(OnClick.class);
}

如果是我們用註解標註的方法則通過註解獲取註解裏保存的空間ID,並且通過MainActivity的實例爲其設置點擊監聽,在監聽內調用被註解標註的方法。

private static int getViewID(Method method) {
    return method.getAnnotation(OnClick.class).value();
}

private static void setOnClickListenerForControl(final Activity activity, final Method method, int viewID) {
    activity.findViewById(viewID).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            try {
                method.invoke(activity);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    });
}

大功告成!是不是很簡單

我們用反射獲取註解的方式實現了ButterKnife的功能,但文章開頭說過反射的存在性能上的不足。而實際上ButterKnife本身用的也不是反射,而是用的apt工具在編譯時期就可以獲取到所有的方法、字段、以及他們的註解,從而避免了使用反射,解決了性能的問題。接下來的文章我會講解本文開頭提到的第三點,也就是ButterKnife實際使用的方法,將我們自己的ButterKnife改爲ButterKnife官方的實現方法。

以下github地址是本篇文章講解用到的demo:
https://github.com/sally519/MyButterKnief

文章很長,希望看完的人能收穫點東西
作者水平有限,如有遺漏和錯誤,歡迎指正!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章