Java注解Annotation

本文將圍繞下面三個(gè)問題展開:

1.Annotation是什么?
2.有什么作用?
3.學(xué)習(xí)理解它有什么好處?

1.Annotation是什么?

從JDK5開始,java增加了對元數(shù)據(jù)(MetaData)的支持,通過Annotation(注解)來實(shí)現(xiàn)。
它可標(biāo)注源碼,在編譯期或運(yùn)行時(shí)被注解處理器處理,然而這些標(biāo)注并不會對源碼產(chǎn)生任何直接的影響。
備注:元數(shù)據(jù)是描述數(shù)據(jù)的數(shù)據(jù)。

分類
  • 一般分類:基本注解,元注解,自定義注解;
  • 注解保留時(shí)間:源碼期注解,編譯期注解,運(yùn)行時(shí)注解;
    下面根據(jù)一般分類進(jìn)行闡述
1.1基本注解

基本注解均位于java.lang包

  • @Override:限定重寫父類方法(僅修飾方法)
    指定方法重載,強(qiáng)制一個(gè)子類必須覆蓋父類的方法。通過該注解,編譯器會在編譯時(shí)進(jìn)行嚴(yán)格的檢查,避免子類中出現(xiàn)重寫方法的方法名錯(cuò)誤等問題。
  • @Deprecated:表示已過時(shí)
    表示某個(gè)程序元素已過時(shí),當(dāng)其他程序使用這些元素時(shí),編譯器會給出警告。
  • @SuppressWarnings:抑制編譯器警告
    指示該Annotation修飾的元素取消顯示指定的編譯器警告,注解會從被注釋的類一直作用到類里的方法和成員變量等。要在注解的括號內(nèi)為value賦值,value的值為需要關(guān)閉的編譯器警告。
  • @SafeVarargs:抑制堆污染警告
    Java7引入,發(fā)生堆污染的代碼使用此注解后,編譯時(shí)程序不會發(fā)出任何警告。
  • @FunctionInterface:函數(shù)式接口(僅修飾接口)
    Java8引入,用來指定某個(gè)接口必須是函數(shù)式接口,通知編譯器進(jìn)行更嚴(yán)格的檢查。
1.2元注解

備注:元注解是和元數(shù)據(jù)一樣,是修飾注解的注解。
java提供的元注解(Meta Annotation),均位于java.lang.annotation包,可修飾其他注解。

  • @Target
    指定被修飾的注解能修飾哪些程序,僅修飾注解。它包含值為value(ElementType類型)的成員變量,使用它必須為該value成員變量指定值,取值如下:
    1.ElementType.ANNOTATION_TYPE:僅修飾Annotation
    2.ElementType.TYPE:接口、類、枚舉、注解
    3.ElementType.FIELD:成員變量(字段、枚舉的常量)
    4.ElementType.METHOD:方法
    5.ElementType.PARAMETER:方法參數(shù)
    6.ElementType.CONSTRUCTOR:構(gòu)造函數(shù)
    7.ElementType.LOCAL_VARIABLE:局部變量
    8.ElementType.PACKAGE:
    9.ElementType.TYPE_PARAMETER:java8引入
    10.ElementType.TYPE_USE:java8引入
  • @Retention
    指定被修飾的注解可以保留多久,僅修飾注解。它包含值為value(RetentionPolicy類型)的成員變量,使用它必須為該value成員變量指定值,取值如下:
    1.RetentionPolicy.SOURCE
    注解只保留在源代碼中(.java文件中),編譯器直接丟棄該注解。
    2.RetentionPolicy.CLASS
    編譯器將把注解記錄在.class文件中。當(dāng)運(yùn)行java程序時(shí),JVM不可獲取注解信息。
    3.RetentionPolicy.RUNTIME
    編譯器將把注解記錄在.class文件中。當(dāng)運(yùn)行java程序時(shí),JVM也可獲取注解信息,程序可以通過反射獲取該注解信息。
  • @Documented
    指定被修飾的注解將被javadoc工具提取成文檔,即該注解將被包含在javadoc中。
  • @Inherited
    指定被修飾的注解具有繼承性,即子類可以繼承父類中的該注解。
  • @Repeatable
    java8引入,用于定義重復(fù)注解
1.3自定義注解

自定義注解要使用@interface關(guān)鍵字。定義注解是可以帶或不帶成員變量,成員變量以抽象方法來聲明,方法名可自定義,返回值可以通過default為成員變量指定默認(rèn)值。設(shè)定了默認(rèn)值的成員變量可以在使用時(shí)不指定值,沒有設(shè)置默認(rèn)值的成員變量必須在使用注解時(shí)傳入值。
自定義注解格式:

public @interface xxx {
    返回值類型 方法名() default 默認(rèn)值;
}
舉例:
public @interface MyTag {
    int value() default 0;
}

根據(jù)Annotation中是否包含成員變量,Annotation可分為兩類:

  • 標(biāo)記注解
    不包含成員變量的注解,利用自身的存在與否來提供信息,如@Override
  • 元數(shù)據(jù)注解
    包含成員變量的注解,為程序提供額外的元數(shù)據(jù)
    備注:注解不提供任何業(yè)務(wù)邏輯處理,僅提供元數(shù)據(jù)或標(biāo)記,如需處理這些標(biāo)記或元數(shù)據(jù),還要自行提取注解信息。

2.Annotation作用

通過問題1,我們知道,注解可以為我們的程序提供標(biāo)記或額外的數(shù)據(jù),以便我們處理相關(guān)業(yè)務(wù)邏輯。
下面舉個(gè)簡單例子說明一下。
如統(tǒng)計(jì)學(xué)生信息,這里就簡單例舉學(xué)生信息有姓名,年齡,性別和住址,其他略,因?yàn)橛写蟛糠謱W(xué)生信息基本相同,如果每錄入都寫一次,會很麻煩,這里使用注解即可完美解決問題。

2.1首先定義一個(gè)學(xué)生信息的注解,如下:
@Target({ElementType.FIELD, ElementType.METHOD})//這里定義該注解適用于方法和成員變量
@Retention(RetentionPolicy.RUNTIME)//這里定義該注解在保留到運(yùn)行時(shí)
public @interface StudentInfo {//學(xué)生信息注解StudentInfo 
    enum Gender {MALE, FEMAIL}//性別枚舉

    String name();//姓名

    int age();//年齡
    //把大部分相同的信息設(shè)置一個(gè)默認(rèn)值,該默認(rèn)值即為多數(shù)相同的信息
    Gender gender() default Gender.MALE;//默認(rèn)性別男

    String address() default "hangzhou";//默認(rèn)地址杭州
}
2.2把上面定義的注解應(yīng)用于邏輯中
public class Student {
    @StudentInfo(name = "xiaomin", age = 10, gender = StudentInfo.Gender.FEMAIL)
    private String xiaominField;//這里的String可以是任意類型,因?yàn)槲覀冇貌恢?    @StudentInfo(name = "zhangsan", age = 12, address = "beijing")
    public String zhangsanField;
    @StudentInfo(name = "lisi", age = 14)
    private String lisiField;
    @StudentInfo(name = "wangwu", age = 16, gender = StudentInfo.Gender.FEMAIL, address = "shanghai")
    private String wangwuField;

    @StudentInfo(name = "xiaomin", age = 8, gender = StudentInfo.Gender.FEMAIL)
    private void xiaominMethod() {
    }

    @StudentInfo(name = "zhangsan", age = 15, address = "beijing")
    public void zhangsanMethod() {
    }

    public void lisiMethod() {
    }

    @StudentInfo(name = "wangwu", age = 10, gender = StudentInfo.Gender.FEMAIL, address = "shanghai")
    public void wangwuMethod() {
    }
}
2.3對應(yīng)用注解的邏輯代碼進(jìn)行處理,獲得最終信息
public class StudentActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_student);
        Student student = new Student();
        processField(student);
        processMethod(student);
    }

    private void processField(Student student) {
        Class<?> clz = student.getClass();
        Field[] declaredFields = clz.getDeclaredFields();
        for (Field field : declaredFields) {
            StudentInfo annotation = field.getAnnotation(StudentInfo.class);
            if (annotation != null) {
                String name = annotation.name();
                int age = annotation.age();
                StudentInfo.Gender gender = annotation.gender();
                String address = annotation.address();
                System.out.println(field.getName() + " ==> " + name + " ==> " + age + " ==> " + gender.toString() + " ==> " + address);
            }
        }
    }

    private void processMethod(Student student) {
        Class<?> clz = student.getClass();
        Method[] declaredMethods = clz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            StudentInfo annotation = method.getAnnotation(StudentInfo.class);
            if (annotation != null) {
                String name = annotation.name();
                int age = annotation.age();
                StudentInfo.Gender gender = annotation.gender();
                String address = annotation.address();
                System.out.println(method.getName() + " ==> " + name + " ==> " + age + " ==> " + gender.toString() + " ==> " + address);
            }
        }
    }
}

獲得信息(打印欄)如下:

System.out: lisiField ==> lisi ==> 14 ==> MALE ==> hangzhou
System.out: wangwuField ==> wangwu ==> 16 ==> FEMAIL ==> shanghai
System.out: xiaominField ==> xiaomin ==> 10 ==> FEMAIL ==> hangzhou
System.out: zhangsanField ==> zhangsan ==> 12 ==> MALE ==> beijing

System.out: wangwuMethod ==> wangwu ==> 10 ==> FEMAIL ==> shanghai
System.out: zhangsanMethod ==> zhangsan ==> 15 ==> MALE ==> beijing
System.out: xiaominMethod ==> xiaomin ==> 8 ==> FEMAIL ==> hangzhou

3.理解Annotation的好處

縱觀Android第三方lib,比如Butterknife,EventBus,Retrofit,Dagger等,里面均有使用注解。如果我們理解了注解,那么閱讀這些優(yōu)秀第三方庫,也是極有幫助的。
備注:閱讀源碼是一件痛并快樂的事情...
下面我就再舉個(gè)例子,仿Butterknife寫個(gè)自定義的View注入工具。

3.1首先定義幾個(gè)自定義的注解:
//setContentView
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    @LayoutRes int value();
}
//findViewById
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    @IdRes int value();
}
//setOnClickListener
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickView {
    int[] value();
}
3.2編寫關(guān)于上面幾個(gè)注解的邏輯代碼(工具類)
    public static void bind(Object obj) {
        contentView(obj);
        bindView(obj);
        clickView(obj);
    }

    private static void contentView(Object obj) {
        Class<?> clz = obj.getClass();
        ContentView contentView = clz.getAnnotation(ContentView.class);
        if (contentView != null) {
            int contentLayoutResId = contentView.value();
            try {
                Method setContentView = clz.getMethod("setContentView", new Class[]{int.class});
                setContentView.invoke(obj, contentLayoutResId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static void bindView(Object obj) {
        Class<?> clz = obj.getClass();
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
            BindView bindView = field.getAnnotation(BindView.class);
            if (bindView != null) {
                int viewId = bindView.value();
                if (obj instanceof Activity) {
                    Activity activity = (Activity) obj;
                    View view = activity.findViewById(viewId);
                    field.setAccessible(true);
                    try {
                        field.set(obj, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private static void clickView(final Object obj) {
        Class<?> clz = obj.getClass();
        Method[] declaredMethods = clz.getDeclaredMethods();
        for (final Method declaredMethod : declaredMethods) {
            ClickView clickView = declaredMethod.getAnnotation(ClickView.class);
            if (clickView != null) {
                declaredMethod.setAccessible(true);
                Object clickListener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return declaredMethod.invoke(obj, args);
                    }
                });

                int[] viewIds = clickView.value();
                for (int viewId : viewIds) {
                    if (obj instanceof Activity) {
                        Activity activity = (Activity) obj;
                        View view = activity.findViewById(viewId);
                        try {
                            Method setOnClickListener = view.getClass().getMethod("setOnClickListener", new Class[]{View.OnClickListener.class});
                            setOnClickListener.invoke(view, clickListener);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    if (obj instanceof Fragment) {

                    }
                }
            }
        }
    }
3.3使用
#activity_main.xml布局#
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <TextView
        android:id="@+id/txt"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:background="@android:color/white"
        android:gravity="center"
        android:text="123"/>

    <Button
        android:id="@+id/btn_set"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:text="設(shè)置TextView值"
        android:textAllCaps="false"/>

    <Button
        android:id="@+id/btn_back"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:text="初始TextView值"
        android:textAllCaps="false"/>
</LinearLayout>

#MainActivity.java#
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.txt)
    TextView mTxt;
    @BindView(R.id.btn_set)
    Button mBtnSet;
    @BindView(R.id.btn_back)
    Button mBtnBack;
    
    private String initTxt = "我是初始TextView值";
    private String lastTxt = "TextView之后設(shè)置的值";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TangBind.bind(this);

        mTxt.setText(initTxt);
        /*mBtnSet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTxt.setText(lastTxt);
            }
        });
        mBtnBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTxt.setText(initTxt);
            }
        });*/
    }

    @ClickView({R.id.btn_set, R.id.btn_back})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_set:
                mTxt.setText(lastTxt);
                break;
            case R.id.btn_back:
                mTxt.setText(initTxt);
                break;
            default:
                break;
        }
    }
}

效果圖如下:

bindview.gif

代碼托管地址:AnnotationDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容