java中的注解Annotation

2018-09-29  本文已影响18人  单向时间轴

概念:

注解(Annotation):是java中的元数据,类、方法、变量、参数都可以被注解。利用注解可以标记源码以便编译器为源码生成文档和检查代码,也可以让编译器和注解处理器在编译时根据注解自动生成代码,甚至可以保留到运行时以便改变运行时的行为。Annotation是java1.5之后引入的,便于实现自定义注解,代替一些配置文件xml的功能,但相对的耦合性变高了。

使用场景

1,用于编译期的注解:使用APT注解处理器(具体如下)生成java文件。

1,概念:APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。简单来说就是在编译期,通过注解生成.java文件。
2,优点:使用APT的优点就是方便、简单,可以少写很多重复的代码。用过ButterKnife、Dagger、EventBus等注解框架的同学就能感受到,利用这些框架可以少写很多代码,只要写一些注解就可以了。其实,他们不过是通过注解,生成了一些代码。同时不影响性能。
3,参考:https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650242955&idx=1&sn=11040755b0df385c5d1facccfc21e107&chksm=88638ee4bf1407f23bb123e61d01e5a454223ce91c05e99a84ee38e9c6a870757d020b1f9f87&mpshare=1&scene=1&srcid=0927xhjYZK52CW7EQ6W9U8SK&pass_ticket=ZvyqJY4H5OcgHznzemnUnwNqUsoVykci2cTsY3fgX3kvWZqymiszHGEk5SUcsJJO#rd

2,用于运行期的注解:使用反射的方式解析,给相应的类,变量,方法等加入功能。(注意:使用反射相对更耗性能,需要慎用)
3,用于一些常用的框架,便于使用的简便(既可能是编译期的注解也有可能使用的运行期注解)。如:ButterKnife,Retrofit等。

语法规则

1,创建一个注解类:

//使用@interface代替class来修饰注解类
public @interface TestAnnomation {
    //使用类似方法的形式来声明变量:age()等效于类当中的age变量
    int age();
    String name();
}

2,方法的返回值

1,八种基本数据类型(byte,short,int,long,boolean,float,double,char)
2,String类型
3,Class类型
4,enum枚举类型
5,Annotation类型
6,上面5中类型的数组,如Class[]

3,元注解的类型

@Target : 限定注解使用的范围(value对应枚举:ElementType)

ElementType.TYPE : 类、接口(包括注解类型)、枚举的声明
ElementType.FIELD : 成员变量字段(包括枚举常量)的声明
ElementType.METHOD : 方法的声明
ElementType.PARAMETER : 形参的声明
ElementType.CONSTRUCTOR : 构造器的声明
ElementType.LOCAL_VARIABLE : 本地变量的声明
ElementType.ANNOTATION_TYPE : 注解类型的声明
ElementType.PACKAGE : 包的声明
ElementType.TYPE_PARAMETER : 泛型参数的声明(1.8之后引入)
ElementType.TYPE_USE : 泛型的使用(1.8之后引入)

@Retention : 说明注解的生命周期(value对应枚举:RetentionPolicy)

RetentionPolicy.SOURCE : 只保留在源码中,会被编译器丢弃
RetentionPolicy.CLASS : 注解会被编译器记录在class文件中,但不需要被VM保留到运行时,这也是默认的行为
RetentionPolicy.RUNTIME : 注解会被编译器记录在class文件中并被VM保留到运行时,所以可以通过反射获取

@Documented : 被注解的元素包含到javadoc文档中

@Inherited : 表明被修饰的注解类型是自动继承的。具体解释如下:若一个注解类型被Inherited元注解所修饰,则当用户在一个类声明中查询该注解类型时,若发现这个类声明中不包含这个注解类型,则会自动在这个类的父类中查询相应的注解类型,这个过程会被重复,直到该注解类型被找到或是查找完了Object类还未找到。

@Repeatable : 可重复注解(1.8后引入),使得作用的注解可以取多个值

注解的解析

1,编译期注解的解析

编译时注解指的是@Retention的值为CLASS的注解,对于这类注解的解析,我们只需做以下两件事:
1,自定义类继承 AbstractProcessor类;
2,重写其中的 process 函数。
然后编译器在编译时会自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法。因此我们只要做好上面两件事,编译器就会主动去解析我们的编译时注解。最常用的就是利用APT编译期生成java代码。

2,运行期注解的解析

运行时注解指的是@Retention的值为RUNTIME 的注解,这类注解可以使用反射的方式进行解析注解的具体逻辑。使用的类为:java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的方法。(具体见下方的自定义注解的实现。)

java中常见的内置注解

1,@Deprecated:用于声明方法或变量等过时

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

2,@Override : 用于声明该方法为覆写的父类方法。

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

3,@SuppressWarnings : 用于声明代码中的警告问题点

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}
//使用如下:
@SuppressWarnings("unchecked"):添加在方法上,取消对方法的检查

4,@SafeVarargs : 参数安全类型注解(1.7后引入),提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}

5,@FunctionalInterface : 表示该接口为函数式接口的注解(1.8后引入:函数式接口 (Functional Interface) = 1个具有1个方法的普通接口)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

Android Support Library 中常见的注解

1,@NonNull : 用于非空判断的注解,如果参数为null则提示警告

@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
public @interface NonNull {
}

2,@UiThread、@WorkerThread : 用于进行线程检查的注解

//在主线程(UI)线程中
@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
public @interface UiThread {
}

@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
public @interface WorkerThread {
}

3,@IdRes : 表明这个整数代表资源引用

@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface IdRes {
}

4,@IntDef、@StringDef : 注解自定义注解来代替枚举

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    int[] value() default {};
    boolean flag() default false;
}

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface StringDef {
    String[] value() default {};
}

自定义注解

1,创建注解类

@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface TestAnnomation {
    int age() default 10;
    String name() default "小明";
}

2,使用反射解析注解

Class,Field,Constructor,Method均实现了接口AnnotatedElement。该接口上提供的两个方法:
1,getAnnotations() (可以获取对象上所有的注解类);
2,getAnnotation(Class<T> annotationClass)(可以获取对象上指定的注解类)

public class Tracker {

    private static final String TAG = "Tracker";

    public static void init(Class<?> clazz) {
        //1,解析类名上的注解
        Annotation[] annotations = clazz.getAnnotations();         //获取类上的所有注解
        TestAnnomation annotation = clazz.getAnnotation(TestAnnomation.class); //获取类上的指定注解
        if (annotation != null) {
            Log.d(TAG, "init: 类上的注解 : " + annotation.age() + " == " + annotation.name());
        }
        //2,解析成员变量上的注解(这里默认成员变量为private)
        Field[] declaredFields = clazz.getDeclaredFields();
        if (declaredFields != null && declaredFields.length > 0) {
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];
//                Annotation[] annotations1 = declaredField.getAnnotations();
                TestAnnomation annotation1 = declaredField.getAnnotation(TestAnnomation.class);
                if (annotation1 != null) {
                    String name = declaredField.getName();
                    Log.d(TAG, "init: 成员变量上的注解 : 变量名 = " + name + " ;; " + annotation1.age() + " == " + annotation1.name());
                }
            }
        }
        //3,解析构造方法上的注解(这里暂时默认构造方法非private)
        Constructor<?>[] constructors = clazz.getConstructors();
        if (constructors != null && constructors.length > 0) {
            for (int i = 0; i < constructors.length; i++) {
                Constructor<?> constructor = constructors[i];
//                Annotation[] annotations1 = constructor.getAnnotations();
                TestAnnomation annotation1 = constructor.getAnnotation(TestAnnomation.class);
                if (annotation1 != null) {
                    Log.d(TAG, "init: 构造方法上的注解 : 参数个数 = " + constructor.getParameterTypes().length + " ;; " + annotation1.age() + " == " + annotation1.name());
                }
            }
        }
        //4,解析方法上的注解(这里暂时默认方法非private)
        Method[] methods = clazz.getMethods();
        if (methods != null && methods.length > 0) {
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
//                Annotation[] annotations1 = method.getAnnotations();
                TestAnnomation annotation1 = method.getAnnotation(TestAnnomation.class);
                if (annotation1 != null) {
                    Log.d(TAG, "init: 方法上的注解 : 方法名 = " + method.getName() + " ;; " + annotation1.age() + " == " + annotation1.name());
                }
            }
        }
    }
}

3,使用注解

@TestAnnomation(age = 20,name = "类名")
public class Test {

    @TestAnnomation(age = 30,name = "变量")
    private int number;
    private String title;

    @TestAnnomation(age = 40,name = "构造方法")
    public Test(int number, String title) {
        this.number = number;
        this.title = title;
    }

    @TestAnnomation(age = 50,name = "方法")
    public int getNumber() {
        return number;
    }

    @TestAnnomation()
    public void setNumber(int number) {
        this.number = number;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

//需要在使用的地方调用方法:Tracker.init(Test.class);

4,测试结果

09-28 11:30:17.875 11448-11448/com.learn.study D/Tracker: init: 类上的注解 : 20 == 类名
    init: 成员变量上的注解 : 变量名 = number ;; 30 == 变量
09-28 11:30:17.876 11448-11448/com.learn.study D/Tracker: init: 构造方法上的注解 : 参数个数 = 2 ;; 40 == 构造方法
    init: 方法上的注解 : 方法名 = getNumber ;; 50 == 方法
09-28 11:30:17.877 11448-11448/com.learn.study D/Tracker: init: 方法上的注解 : 方法名 = setNumber ;; 10 == 小明
上一篇下一篇

猜你喜欢

热点阅读