Java注解Annotation

本文将围绕下面三个问题展开:

1.Annotation是什么?
2.有什么作用?
3.学习理解它有什么好处?

1.Annotation是什么?

从JDK5开始,java增加了对元数据(MetaData)的支持,通过Annotation(注解)来实现。
它可标注源码,在编译期或运行时被注解处理器处理,然而这些标注并不会对源码产生任何直接的影响。
备注:元数据是描述数据的数据。

分类
  • 一般分类:基本注解,元注解,自定义注解;
  • 注解保留时间:源码期注解,编译期注解,运行时注解;
    下面根据一般分类进行阐述
1.1基本注解

基本注解均位于java.lang包

  • @Override:限定重写父类方法(仅修饰方法)
    指定方法重载,强制一个子类必须覆盖父类的方法。通过该注解,编译器会在编译时进行严格的检查,避免子类中出现重写方法的方法名错误等问题。
  • @Deprecated:表示已过时
    表示某个程序元素已过时,当其他程序使用这些元素时,编译器会给出警告。
  • @SuppressWarnings:抑制编译器警告
    指示该Annotation修饰的元素取消显示指定的编译器警告,注解会从被注释的类一直作用到类里的方法和成员变量等。要在注解的括号内为value赋值,value的值为需要关闭的编译器警告。
  • @SafeVarargs:抑制堆污染警告
    Java7引入,发生堆污染的代码使用此注解后,编译时程序不会发出任何警告。
  • @FunctionInterface:函数式接口(仅修饰接口)
    Java8引入,用来指定某个接口必须是函数式接口,通知编译器进行更严格的检查。
1.2元注解

备注:元注解是和元数据一样,是修饰注解的注解。
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:方法参数
    6.ElementType.CONSTRUCTOR:构造函数
    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文件中。当运行java程序时,JVM不可获取注解信息。
    3.RetentionPolicy.RUNTIME
    编译器将把注解记录在.class文件中。当运行java程序时,JVM也可获取注解信息,程序可以通过反射获取该注解信息。
  • @Documented
    指定被修饰的注解将被javadoc工具提取成文档,即该注解将被包含在javadoc中。
  • @Inherited
    指定被修饰的注解具有继承性,即子类可以继承父类中的该注解。
  • @Repeatable
    java8引入,用于定义重复注解
1.3自定义注解

自定义注解要使用@interface关键字。定义注解是可以带或不带成员变量,成员变量以抽象方法来声明,方法名可自定义,返回值可以通过default为成员变量指定默认值。设定了默认值的成员变量可以在使用时不指定值,没有设置默认值的成员变量必须在使用注解时传入值。
自定义注解格式:

public @interface xxx {
    返回值类型 方法名() default 默认值;
}
举例:
public @interface MyTag {
    int value() default 0;
}

根据Annotation中是否包含成员变量,Annotation可分为两类:

  • 标记注解
    不包含成员变量的注解,利用自身的存在与否来提供信息,如@Override
  • 元数据注解
    包含成员变量的注解,为程序提供额外的元数据
    备注:注解不提供任何业务逻辑处理,仅提供元数据或标记,如需处理这些标记或元数据,还要自行提取注解信息。

2.Annotation作用

通过问题1,我们知道,注解可以为我们的程序提供标记或额外的数据,以便我们处理相关业务逻辑。
下面举个简单例子说明一下。
如统计学生信息,这里就简单例举学生信息有姓名,年龄,性别和住址,其他略,因为有大部分学生信息基本相同,如果每录入都写一次,会很麻烦,这里使用注解即可完美解决问题。

2.1首先定义一个学生信息的注解,如下:
@Target({ElementType.FIELD, ElementType.METHOD})//这里定义该注解适用于方法和成员变量
@Retention(RetentionPolicy.RUNTIME)//这里定义该注解在保留到运行时
public @interface StudentInfo {//学生信息注解StudentInfo 
    enum Gender {MALE, FEMAIL}//性别枚举

    String name();//姓名

    int age();//年龄
    //把大部分相同的信息设置一个默认值,该默认值即为多数相同的信息
    Gender gender() default Gender.MALE;//默认性别男

    String address() default "hangzhou";//默认地址杭州
}
2.2把上面定义的注解应用于逻辑中
public class Student {
    @StudentInfo(name = "xiaomin", age = 10, gender = StudentInfo.Gender.FEMAIL)
    private String xiaominField;//这里的String可以是任意类型,因为我们用不着
    @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对应用注解的逻辑代码进行处理,获得最终信息
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等,里面均有使用注解。如果我们理解了注解,那么阅读这些优秀第三方库,也是极有帮助的。
备注:阅读源码是一件痛并快乐的事情...
下面我就再举个例子,仿Butterknife写个自定义的View注入工具。

3.1首先定义几个自定义的注解:
//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编写关于上面几个注解的逻辑代码(工具类)
    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="设置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之后设置的值";

    @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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容