Java Annotation 簡析

一. Annotation 概念

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

註解是一種元數據, 可以添加到java代碼中. 類、方法、變量、參數、包都可以被註解,註解對註解的代碼沒有直接影響。

註解(Annotation)在JDK1.5之後增加的一個新特性,註解的引入意義很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用註解。

定義註解用的關鍵字是@interface。


二. Annotation 示例

Override Annotation

@Override
public void onCreate(Bundle savedInstanceState);

Retrofit Annotation

@GET("/users/{username}")
User getUser(@Path("username") String username);

Butter Knife Annotation

@InjectView(R.id.user) 
EditText username;

注:

Retrofit 爲符合 RESTful 規範的網絡請求框架
Butter Knife 爲 View 及事件等依賴注入框架


三. Annotation作用

a.生成文檔。這是最常見的,也是java 最早提供的註解。常用的有@see @param @return 等

b. 標記,用於告訴編譯器一些信息,

c. 編譯時動態處理,如動態生成代碼

d. 運行時動態處理,如得到註解信息


四. Annotation 分類

4.1 標準 Annotation,Override, Deprecated, SuppressWarnings

標準 Annotation 是指 Java 自帶的幾個 Annotation,上面三個分別表示重寫函數,不鼓勵使用(有更好方式、使用有風險或已不在維護),忽略某項 Warning

4.2 元 Annotation,@Retention, @Target, @Inherited, @Documented

元 Annotation 是指用來定義 Annotation 的 Annotation。

4.3 自定義 Annotation

自定義 Annotation 表示自己根據需要定義的 Annotation,定義時需要用到上面的元 Annotation
這裏是一種分類而已,也可以根據作用域分爲源碼時、編譯時、運行時 Annotation,後面在自定義 Annotation 時會具體介紹。


五. 元註解

元註解就是用來定義註解的註解.其作用就是定義註解的作用範圍, 使用在什麼元素上等等, 下面來詳細介紹.

元註解共有四種:

@Retention 

 @Target

@Inherited 

 @Documented


5.1 @Retention 保留的範圍,默認值爲CLASS. 可選值有三種

            SOURCE, 只在源碼中可用,即此類註解只在源碼中保留

            CLASS, 在源碼和字節碼中可用,即此類註解可以保留到編譯時

            RUNTIME, 在源碼,字節碼,運行時均可用,即此類註解在運行時還是可用


5.2 @Target 用來修飾程序中的那些元素,即註解的使用範圍,  如 TYPEMETHODCONSTRUCTOR,FIELDPARAMETER等,未標註則表示可修飾所有。

                 ElementType.CONSTRUCTOR:描述構造器

             ElementType.FIELD:描述成員變量

             ElementType.LOCAL_VARIABLE: 描述局部變量

             ElementType.METHOD: 描述方法

             ElementType.PACKAGE: 描述包

             ElementType.PARAMETER:描述方法的參數

             ElementType.Type: 描述類,接口(包括註解類型)或enum聲明.


5.3 @Inherited 是否可以被繼承,默認爲false,即父類中使用了一個Annotation,則子類繼承父類的這個annotation,annotation需要標記爲RUNTIME的纔可以。


5.4 @Documented 是否會保存到 Javadoc 文檔中


其中, @Retention是定義保留策略, 直接決定了我們用何種方式解析. SOUCE級別的註解是用來標記的, 比如Override, SuppressWarnings. 我們真正使用的類型是CLASS(編譯時)和RUNTIME(運行時)

java 內置註解

@Override

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

從前面的元註解介紹可以看到,Override用於標註方法,有效期是在源碼期間。用於標註方法重寫。

@Deprecated

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}

標註 過時或者不建議使用,也是會保留到運行時,添加了Documented元標籤,這樣在生成文檔時候,就可以生成過時的標記。

@SuppressWarnings

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

忽略錯誤報告,有效時是源碼級。


六. 自定義註解

舉個栗子, 結合例子講解,我們在android開發中,可能最先接觸的就是findViewById(),如果一個activity中View有很多個的話,想必findViewById()肯定是一大籮筐了,我們這裏實現個類似ButterKnife的功能,用註解代替findViewById()。首先定義一個ViewBind註解:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ViewBind {
    int value() default 0;
}

如上,ElementType.FIELD表示只能使用在成員變量上,RetentionPolicy.RUNTIME表示註解可以保留在運行時,上面的value()是註解類的方法,其寫法爲:

類型 參數名() default 默認值;

其中默認值是可選的, 可以定義, 也可以不定義,註解類的方法有以下特點:

a. 註解配置參數名爲註解類的方法名

b. 所有方法沒有方法體,沒有參數,沒有修飾符,實際只允許 public & abstract 修飾符,默認爲 public,不允許拋異常

c. 方法返回值只能是基本類型,String, Class, annotation, enumeration 或者是他們的一維數組

d. 若只有一個默認屬性,可直接用 value() 函數。一個屬性都沒有表示該 Annotation 爲 Mark Annotation

e. 可以加 default 表示默認值


自定義好註解後,我們就可以使用了,在activity中我們就可以如下使用:

 @ViewBind(R.id.user_tv)
 TextView mTextView;

OK,自定義註解完工了麼?開玩笑,當然不是這麼簡單就可以在activity中丟掉findViewById()的,上面我們只是定義好了註解類,然而註解類的實際功能還沒有,要實現上面註解的功能需要用到java反射技術。

public class ViewBindUtil {

    public static void initViewBind(Activity activity){
        Class cls = activity.getClass(); //獲取Class類
        Field[] declaredFields = cls.getDeclaredFields();  // 拿到所有Field
        for(Field field : declaredFields){
            // 獲取Field上的註解
            ViewBind annotation = field.getAnnotation(ViewBind.class);
            if(annotation != null){
                // 獲取註解值
                int id = annotation.value();
                try {
                    field.setAccessible(true); //設置可訪問
                    field.set(activity,activity.findViewById(id)); //設置findViewById值
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
上面就是利用java反射取得添加了我們定義的ViewBind註解的成員,然後設置findViewById值,下面看看最終的activity代碼:

public class MainActivity extends AppCompatActivity {
    @ViewBind(R.id.user_tv)
    TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewBindUtil.initViewBind(this);
        mTextView.setText("hahaha!!!! annotation success!!!!");
   }
}

運行下,會發現mTextView成功顯示了上面的設置的字符串內容,我們成功丟掉了findViewById()方法,我們還可以再加上點擊事件的註解,丟掉煩人的setOnClickListener()

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ViewClick {
    int id();
}
ElementType.METHOD代表只能作用在類方法上,再修改註解的功能實現類,增加ViewClick修飾的方法調用:

public class ViewBindUtil {

    public static void initViewBind(final Activity activity){
        Class cls = activity.getClass(); //獲取Class類
        Field[] declaredFields = cls.getDeclaredFields();  // 拿到所有Field
        for(Field field : declaredFields){
            // 獲取Field上的註解
            ViewBind annotation = field.getAnnotation(ViewBind.class);
            if(annotation != null){
                // 獲取註解值
                int id = annotation.value();
                try {
                    field.setAccessible(true); //設置可訪問
                    field.set(activity,activity.findViewById(id)); //設置findViewById值
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        Method[] methods = cls.getDeclaredMethods();
        for(final Method method:methods){
            // 獲取Field上的註解
            ViewClick annotation = method.getAnnotation(ViewClick.class);
            if(annotation != null){
                int id = annotation.id();
                //設置點擊事件
                activity.findViewById(id).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            method.invoke(activity, new Class[0]); //調用自定義的點擊事件
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
}
下面是activity中的寫法:

public class MainActivity extends AppCompatActivity {
    final static String TAG = "MainActivity";

    @ViewBind(R.id.user_tv)
    TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewBindUtil.initViewBind(this);
        mTextView.setText("hahaha!!!! annotation success!!!!");
    }

    @ViewClick(id = R.id.user_tv)
    public void onClick(){
        Toast.makeText(this,"hahaha!!!! annotation click success!!!!",Toast.LENGTH_SHORT).show();
    }
}

上面我們就是在activity裏面有一個textview,點擊textview會彈出Toast,下面看看總的效果:

通過註解,我們達到了不用在activity裏使用findViewById()和setOnclickListener的目的,以簡化代碼。這樣很有成就感呀,但是,上面都是通過在app運行時使用java反射技術來實現的,我們知道反射存在很大的性能問題,能不用時儘量不用。那麼有沒有其他方式實現上面的功能呢?這時我們就需要使用編譯註解了。下回我們繼續分享。



更多精彩Android技術可以關注我們的微信公衆號,掃一掃下方的二維碼或搜索關注公共號: Android老鳥

                                                




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章