美文网首页
java中的注解Annotation

java中的注解Annotation

作者: 单向时间轴 | 来源:发表于2018-09-29 14:36 被阅读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 == 小明
    

    相关文章

      网友评论

          本文标题:java中的注解Annotation

          本文链接:https://www.haomeiwen.com/subject/gacfoftx.html