android&java註解詳解

通過拜讀《java編程思想》中的註解篇,談談自己對註解中的理解!

學習android經常會用到註解,但是對於註解也是半懵逼狀態,至於它是怎麼來的,怎麼起作用是什麼都不知道;比如熟悉的@Override,@Deprecated……等等只知道是註解,以及其起到的作用,至於它是或不是java中的語法,怎麼由來怎麼讀取的其實是沒探究的。本文起到一個拋磚引玉的作用,探索註解的由來以及如何自定義自己的註解

一、註解是什麼

       《java編程思想》的定義:註解(也被成爲元數據)爲我們在代碼中添加信息提供了一種形式化的方法,使我們可以在稍後某個時刻非常方便地使用這些數據。

        看完這個定義如果是沒接觸註解的我相信很多開發者都處於懵懂狀態,也不知道怎麼用。其實具體點的就是給類、方法、屬性附加一些說明信息,等到想用的時候在讀取。而且這個信息(元數據)是同源代碼一起結合在一起的,所以可通過java代碼獲取到註解信息。

        如系統的@Override:說明信息就是:指名這個方法是重載的方法;這個例子沒有值不夠形象,往下讀,等會會有例子,相信很快你就知道了

 

二、定義

      註解的定義很想java定義接口:如   

public @interface CupAnnotation {}

     這樣就定義好了一個註解類,是不是特別像接口,但是與接口不一樣的是,它裏邊定義的方法可以有默認值 如:

@Target({ElementType.METHOD, ElementType.FIELD}) //方法、屬性都可以和使用註解
@Retention(RetentionPolicy.RUNTIME)  //vm運行期間保留註解 通過反射機制獲取註解信息
public @interface CupAnnotation {
    int id() default -1;

    String color() default "white";

    String shaper() default "";

    String methodName() default "defualt";
}

接口中是沒有@符號的,而且聲明的方法是不會有default ;

而註解是通過@interface來指明這是一個註解類,default “呵呵”是方法的默認值;


默認值的作用:如果有這個沒有這個默認值,則在使用註解的使用必須給其賦值,如果有默認值可以不給其賦值,會                          默認使用指定的默認值。如熟悉的@Override 這就是典型的不需要指定值,使用的都是默認值


三、使用

     註解的使用,相信學習android或者java的很熟悉系統中自帶的@Override,@Deprecated等等了,但是如何使用自定義註解,怎麼給賦值,如何讀取這些值很可能還只是一個謎團。
    註解可以與java中的任何修飾符共同作用於方法,如:public、static或void,註解的使用方式幾乎與修飾符一模一樣。
   這裏使用我通過一個demo來講解;但是在講解之前先介紹一些元註解。java中系統通過只內置了7種標準的元註解:可以用它們來先定我們自定義的作用
  @Override @Deprecated @SuppressWarning 以及@Target @Retention @Document @Inherited。’  
  這又是什麼鬼?細心的你,在自定義自己的註解時是不是有用到@Target和@Retention?
  《java編程思想》的圖示:
   
   由於前邊三個自定義註解時不怎麼用上,我們就不說了,這裏比較常用的可能就是@Target和@Retention了。@Target是指明這個註解只能作用於那個屬性、類、方法等等上邊列出支持的,如果沒指明則默認都支持;而@Retention只的是什麼級別保存信息,即在運行時期或編譯期保留或丟棄;比如RUNTIME則在運行期時可以利用反射機制獲取註解信息,如果是其它兩個就沒法使用反射獲取元數據,因爲註解已經被編譯器在編譯階段丟掉,運行時就沒有註解的信息,所以是無法獲取到的。
要注意,如果是註解使用多個支持,則是數組要用{}包裹,註解的屬性通過“,”分割開:如果不是數組則直接使用“,”分割開就行:如:數組@Test({"str",“23”})   非數組 @Test(test1 = "helloworld",test2=23)
接下來看看demo:

一、聲明一個cup的註解
package com.example.zwr.annotationdemo;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * author : zhongwr on 2016/12/1
 * 用以杯子的註解
 */
@Target({ElementType.METHOD, ElementType.FIELD}) //方法、屬性都可以使用註解
@Retention(RetentionPolicy.RUNTIME)  //vm運行期間保留註解 通過反射機制獲取註解信息
public @interface CupAnnotation {
    int id() default -1;

    String color() default "white";

    String shaper() default "";

    String methodName() default "defualt";
}
upAnnotation(methodName = "DefualtCup of getColor()") public String getColor() { return "color = defualt"; } public String getId() { return "id = -1"; } public String getShape() { return "shaper = defualt"; }} 定義這個註解值得注意的是下邊這兩行:
@Target({ElementType.METHOD, ElementType.FIELD}) //方法、屬性都可以使用註解
@Retention(RetentionPolicy.RUNTIME)  //vm運行期間保留註解 通過反射機制獲取註解信息
說明它的作用,否則在運行時是無法生效的,可以查看java中用到註解的源碼,跟進去它們的定義也會有這兩個

定義好後,我們聲明幾個實體類,通過註解來爲其說明這個實體類的信息:說明杯子的形狀/id/color

cup:爲綠色的圓形cup,id值爲1 color
/**
 * author : zhongwr on 2016/12/1
 */
public class CircleGreenCup {
    @CupAnnotation(id = 1)
    private int ids;
    @CupAnnotation(color = "green")
    private String color;
    @CupAnnotation(shaper = "圓形")
    private String shape;

    public String getColor() {
        return "color = red";
    }
    @CupAnnotation(methodName = "circleGreenCup of getId")
    public String getId() {
        return "id = 1";
    }
    public String getShape() {
        return "shaper = 圓的";
    }
}
將cup聲明爲:紅色的正方形cup
/**
 * author : zhongwr on 2016/12/1
 * 使用聲明註解類的默認值
 */
public class SquareRedCup {
    @CupAnnotation(id = 2)
    private int ids;
    @CupAnnotation(color = "Square")
    private String color;
    @CupAnnotation(shaper = "正方形")
    private String shape;

    public String getColor() {
        return "color = red";
    }

    public String getId() {
        return "id = 2";
    }
    @CupAnnotation(methodName = "SquareRedCup of getShape()")
    public String getShape() {
        return "shaper = 正方形";
    }
}

默認值:即使用註解的時候不給賦值,前邊兩個都會相應修改器默認值
package com.example.zwr.annotationdemo.model;

import com.example.zwr.annotationdemo.CupAnnotation;

/**
 * author : zhongwr on 2016/12/1
 * 使用聲明註解類的默認值
 */
public class DefualtCup {
    @CupAnnotation
    private int ids;
    @CupAnnotation
    private String color;
    @CupAnnotation
    private String shape;

    @CupAnnotation(methodName = "DefualtCup of getColor()")
    public String getColor() {
        return "color = defualt";
    }

    public String getId() {
        return "id = -1";
    }
    public String getShape() {
        return "shaper = defualt";
    }
}

可以看到:註解修改默認值時可以只指定要修改的默認值,而不需要都修改,如:只修改形狀而不修改id和顏色
@CupAnnotation(shaper = "圓形")
此外:注意到我們使用註解賦值方式:直接用的就是註解聲明的方法名來賦值的,如註解類Test的方法是 int id();那麼在使用賦值的時候就用 如@Test(id=23)來爲其賦值

解析註解:即獲取註解的信息如下:
package com.example.zwr.annotationdemo;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * author : zhongwr on 2016/12/1
 * 獲取杯子的註解
 */
public class CupAnnotationUtil {
    public static String getAnnotationInfo(Class<?> targetClass) {
        StringBuilder annotationInfo = new StringBuilder();
        //判斷targetClass類聲明是否用了註解:如在類聲明前用了註解則可通過這個獲取類註解的value  舉例:@CupAnnotation public class Cupdemo{}
        CupAnnotation cupAnnotation = targetClass.getAnnotation(CupAnnotation.class);
        annotationInfo.append("類聲明用的註解:");
        annotationInfo.append("\n");
        annotationInfo.append("類 :" + targetClass.getName());
        if (null == cupAnnotation) {
            annotationInfo.append("類聲明沒用到註解 :");
            annotationInfo.append("\n");
        } else {
            annotationInfo.append("杯子標籤:" + cupAnnotation.id() + " 杯子顏色:" + cupAnnotation.color() + " 杯子形狀:" + cupAnnotation.shaper());
        }

        annotationInfo.append("\n");
        annotationInfo.append("方法聲明用的註解:");
        annotationInfo.append("\n");
        //判斷targetClass所有方法是否有用到註解
        Method methods[] = targetClass.getDeclaredMethods();
        if (null != methods) {
            int length = methods.length;
            for (int i = 0; i < length; i++) {
                CupAnnotation cupMethodAnno = methods[i].getAnnotation(CupAnnotation.class);
                if (null != cupMethodAnno) {//方法中使用了註解則不會爲空
                    annotationInfo.append("註解方法名:" + cupMethodAnno.methodName());
                } else {
                    annotationInfo.append("沒使用註解的方法名:" + methods[i].getName());
                }
                annotationInfo.append("\n");
            }
        }

        annotationInfo.append("\n");
        annotationInfo.append("屬性聲明用的註解:");
        annotationInfo.append("\n");
        //判斷targetClass所有屬性是否有用到註解
        Field[] fields = targetClass.getDeclaredFields();
        if (null != fields) {
            int length = fields.length;
            for (int i = 0; i < length; i++) {
                CupAnnotation cupFieldAnno = fields[i].getAnnotation(CupAnnotation.class);
                if (null != cupFieldAnno) {//方法中使用了註解則不會爲空
                    annotationInfo.append("屬性註解信息:");
                    annotationInfo.append("\n");
                    annotationInfo.append("屬性名:" + fields[i].getName() + " 杯子標籤:" + cupFieldAnno.id() + " 杯子顏色:" + cupFieldAnno.color() + " 杯子形狀:" + cupFieldAnno.shaper());
                } else {
                    annotationInfo.append("沒使用註解的屬性名:" + fields[i].getName());
                }
                annotationInfo.append("\n");
            }
        }
        return annotationInfo.toString();
    }
}

一:類聲明的註解:通過如下方式獲取一個註解類:如果不爲null這表示在類聲明時用了註解,如果爲null則類聲明沒用註解
 CupAnnotation cupAnnotation = targetClass.getAnnotation(CupAnnotation.class)

二:方法聲明的註解:通過如下方式獲取一個註解類:如果不爲null這表示在方法聲明時用了註解,如果爲null則方法聲明沒用註解
 CupAnnotation cupMethodAnno = methods[i].getAnnotation(CupAnnotation.class);


二:屬性聲明的註解:通過如下方式獲取一個註解類:如果不爲null這表示在屬性聲明時用了註解,如果爲null則屬性聲明沒用註解
  CupAnnotation cupFieldAnno = fields[i].getAnnotation(CupAnnotation.class);

獲取信息方式都是通過這個註解類對象直接調用其方法來獲取其值,具體看上邊例子的獲取。
由於作者是做android 的所以通過android的一個demo來測試:
package com.example.zwr.annotationdemo;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.example.zwr.annotationdemo.model.CircleGreenCup;
import com.example.zwr.annotationdemo.model.DefualtCup;
import com.example.zwr.annotationdemo.model.SquareRedCup;
import com.example.zwr.annotationdemo.model.UnuseCupAnnotation;

public class MainActivity extends Activity implements View.OnClickListener {

    private static final String TAG = "MainActivity";
    private TextView tvGetAnnotationInfo;
    private TextView tvShowAnnotationInfo;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
    }

    private void initViews() {
        tvGetAnnotationInfo = (TextView) findViewById(R.id.tv_get_annotation_info);
        tvShowAnnotationInfo = (TextView) findViewById(R.id.tv_show_annotation_info);
        tvGetAnnotationInfo.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v == tvGetAnnotationInfo) {
            String annotationContent = CupAnnotationUtil.getAnnotationInfo(DefualtCup.class) + "\n" +
                    CupAnnotationUtil.getAnnotationInfo(CircleGreenCup.class) + "\n" +
                    CupAnnotationUtil.getAnnotationInfo(SquareRedCup.class) + "\n" +
                    CupAnnotationUtil.getAnnotationInfo(UnuseCupAnnotation.class);

            Log.d(TAG, annotationContent);
            tvShowAnnotationInfo.setText(annotationContent);
        }
    }
}

當點擊按鈕後,將解析的註解信息顯示在TextView上:信息如下:

懶得錄製,就放這麼一小段信息,你也可以下載demo自己跑一下就知道了;

這裏注意一點:註解類沒有繼承,但是可以使用組合,組合的方式聲明:demo沒有說明,感興趣的可以試試;舉個栗子:

註解類1 Test1:
public @interface Test1{
    int id() defualt -1;
    String getTestInfo() defualt "Test";
}

註解類2 Test2:
public @interface Test2{
    int id() defualt -1;
    String getTestInfo() defualt "Test";
    //這裏使用組合,使用Test1的註解
    Test1 testInstance() defualt @Test1(id = 1);
}


調用的使用就跟調用方法一樣就可以返回一個Test1實例了,如 Test1 test1 = test2.testInstance();

既然是學android的,那麼我們學了有什麼用處呢?
其實android的用處大了去了,比如我們常見的插件butterKnife,數據庫框架OrmLite等等都是通過註解去完成實現的。

擴展:這裏只是提到,感興趣的自行了解:
    java中可通過apt工具去解析註解,以及通過ProcessFiles查找遍歷所有文件;可通過字節碼獲取精確讀取class文件以及通過ClassPool修改java文件的內容,這是多麼強大的功能,as插件也可以做到修改java文件內容,而不需要讀寫文件;以上內容可在《java編程思想》一書中的第20章註解的單元測試小節中找到大概是630頁左右
as的插件開發請見另一篇博文:

到這裏基本就結束了: 有什麼問題大家可以一起探討,若有理解不對的地方請不吝指正!




註解類1 Test1:
public @interface Test1{
    int id() defualt -1;
    String getTestInfo() defualt "Test";
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章