zoukankan      html  css  js  c++  java
  • Java_注解_01_注解(Annotation)详解

    一、注解的概念

    Annotation(注解)是插入代码中的元数据(元数据从metadata一词译来,就是“描述数据的数据”的意思),在JDK5.0及以后版本引入。它可以在编译期使用预编译工具进行处理, 也可以在运行期使用 Java 反射机制进行处理,用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。因为本质上,Annotion是一种特殊的接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。

    二、注解的本质

    2.1 通过示例看清本质

    注解本质上是一种继承自接口`java.lang.annotation.Annotation`的特殊接口。

    一个自定义注解的示例:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface PersonAnno {
    
        String name() default "";
        int age() default 0;
    }
    View Code

    本质上注解会被编译为继承了(Annotation接口)的接口,反编译上面的PersonAnno.class可以看到代码如下: 

    这里写图片描述

    2.2 注解源码

    java.lang.annotation.Annotation类源码如下:

    /**
     * The common interface extended by all annotation types.  Note that an
     * interface that manually extends this one does <i>not</i> define
     * an annotation type.  Also note that this interface does not itself
     * define an annotation type.
     *
     * @author  Josh Bloch
     * @since   1.5
     */
    /**
     * 首先声明英语不是很好,大致意思是:这是一个基础接口,所有的注解类型都继承与它。但是需要注意的是
     * (1)不需要手动指明一个注解类型是继承与它的(意思是自动继承)
     * (2)它本身不是注解类型
     */
    public interface Annotation {
        /**
         * 这三个方法就不用多说了吧!
         */
        boolean equals(Object obj);
       
        int hashCode();
    
        String toString();
    
        /**
         * Returns the annotation type of this annotation.
         */
        /**
         * 返回注解的class
         */
        Class<? extends Annotation> annotationType();
    }
    View Code

    2.3 注解本质的总结

    (1)注解实质上会被编译器编译为接口,并且继承java.lang.annotation.Annotation接口。

    (2)注解的成员变量会被编译器编译为同名的抽象方法。

    (3)根据Java的class文件规范,class文件中会在程序元素的属性位置记录注解信息。例 如,RuntimeVisibleAnnotations属性位置,记录修饰该类的注解有哪些;flags属性位置,记录该类是不是注解;在方法的 AnnotationDefault属性位置,记录注解的成员变量默认值是多少。 

    三、注解的作用

    Annotation的作用大致可分为三类:

    (1)编写文档:通过代码里标识的元数据生成文档;

    (2)代码分析:通过代码里标识的元数据对代码进行分析;

    (3)编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查;

    综上所述可知,Annotation主要用于提升软件的质量和提高软件的生产效率。

    四、注解的分类

    4.1 注解分类

    4.1.1 根据成员个数分类

    (1)标记注解:没有定义成员的Annotation类型,自身代表某类信息,如:@Override

    (2)单成员注解:只定义了一个成员,比如@SuppressWarnings 定义了一个成员String[] value,使用value={…}大括号来声明数组值,一般也可以省略“value=”

    (3)多成员注解:定义了多个成员,使用时以name=value对分别提供数据

    4.1.2 根据注解使用的功能和用途分类

    (1)Java内置注解:Java自带的注解类型

                    @Override:用于修饰此方法覆盖了父类的方法;

                    @Deprecated:用于修饰已经过时的方法;

                    @SuppressWarnnings:用于通知java编译器禁止特定的编译警告;

    (2)元注解:注解的注解,负责注解其他注解

                   @Target:用于描述注解可以修饰的类型

                   @Retention:用于声明注解的生命周期,即注解在什么范围内有效。

                   @Documented:是一个标记注解,表明含有该注解类型的元素(带有注释的)会通过javadoc或类似工具进行文档化。

                   @Inherited:是一个标记注解,表示该注解类型能被自动继承。

                   @Repeatable :规定注解是否可以重复,重复型的注解还需要指明注解容器,用来存储可重复性注解,同样也是 Java 8 之后才支持

    (3)自定义注解:用户根据自己的需求自定义的注解类型

           使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口

    五、Java内置注解

    5.1 @Override(覆写) ——限定重写父类方法

    (1)源码:

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

    (2)分析:

    @Override 是一个标记注解,标注于方法上,仅保留在java源文件中。

    (3)用途:

    用于告知编译器,我们需要覆写超类的当前方法。如果某个方法带有该注解但并没有覆写超类相应的方法,则编译器会生成一条错误信息。

    5.2 @Deprecated(不赞成使用)——用于标记已过时方法

    (1)源码:

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

    (2)分析:

    @Deprecated 是一个标记注解,可标注于除注解类型声明之外的所有元素,保留时长为运行时VM。

    (3)用途:

    用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。

    5.3 @SuppressWarnings(抑制警告)——抑制编译器警告

    (1)源码:

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

    (2)分析:

     @SuppressWarnings有一个类型为String[]的成员,不是标记注解,这个成员的值为被禁止的警告名,可标注于除注解类型声明和包名之外的所有元素,仅保留在java源文件中。

    (3)用途:

    用于告知编译器忽略特定的警告信息。

    (4)使用示例:

    public class SuppressWarningTest {
        @SuppressWarnings("unchecked")
        public void addItems2(String item){
            @SuppressWarnings("unused")
            List list = new ArrayList();
            List items = new ArrayList();
            items.add(item);
        }
    
        @SuppressWarnings({"unchecked","unused"})
        public void addItems1(String item){
            List list = new ArrayList();
            list.add(item);
        }
    
        @SuppressWarnings("all")
        public void addItems(String item){
            List list = new ArrayList();
            list.add(item);
        }
    }
    View Code

    (5)常见参数值

    该注解有方法value(),可支持多个字符串参数,例如:

    @SupressWarning(value={"uncheck","deprecation"})

    前面讲的@Override,@Deprecated都是无需参数的,而压制警告是需要带有参数的,可用参数如下:

    参数含义
    deprecation 使用了过时的类或方法时的警告
    unchecked 执行了未检查的转换时的警告
    fallthrough 当Switch程序块进入进入下一个case而没有Break时的警告
    path 在类路径、源文件路径等有不存在路径时的警告
    serial 当可序列化的类缺少serialVersionUID定义时的警告
    finally 任意finally子句不能正常完成时的警告
    all 以上所有情况的警告
    更多关键字  

    六、元注解

    元注解的作用就是负责注解其他注解

    6.1 @Target

    (1)源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        ElementType[] value();
    }

    (2)作用:

    用于描述注解可以修饰的程序元素。当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。

    如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。程序元素(ElementType)是枚举类型,共定义8种程序元素,如下表:

    ElementType含义
    ANNOTATION_TYPE 注解类型声明
    CONSTRUCTOR 构造方法声明
    FIELD 字段声明(包括枚举常量)
    LOCAL_VARIABLE 局部变量声明
    METHOD 方法声明
    PACKAGE 包声明
    PARAMETER 参数声明
    TYPE 类、接口(包括注解类型)或枚举声明

    例如,上面源码@Target的定义中有一行  @Target(ElementType.ANNOTATION_TYPE) ,意思是指当前注解的元素类型是注解类型。

    6.2 @Retention

    (1)源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        RetentionPolicy value();
    }

    (2)作用:

    描述该注解的生命周期,表示在什么编译级别上保存该注解的信息。

    当注解类型声明中没有@Retention元注解,则默认保留策略为RetentionPolicy.CLASS。

    保留策略(RetentionPolicy)是枚举类型,共定义3种保留策略,如下表:

    RetentionPolicy含义
    SOURCE 仅存在Java源文件,经过编译器后便丢弃相应的注解
    CLASS 存在Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注释
    RUNTIME 存在源文件、编译生成的Class字节码文件,以及保留在运行时VM中,可通过反射性地读取注解

    例如,上面源码@Retention的定义中有一行 @Retention(RetentionPolicy.RUNTIME),意思是指当前注解的保留策略为RUNTIME,即存在Java源文件,也存在经过编译器编译后的生成的Class字节码文件,同时在运行时虚拟机(VM)中也保留该注解,可通过反射机制获取当前注解内容。

    6.3 @Documented

    (1)源码:

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

    (2)作用:

    是一个标记注解,表示拥有该注解的元素可通过javadoc此类的工具进行文档化,即在生成javadoc文档的时候将该Annotation也写入到文档中。

    例如,上面源码@Retention的定义中有一行 @Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。

    6.4 @Inherited

    (1)源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }

    (2)作用:

    是一个标记注解,表示该注解类型被自动继承。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

    当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

    七、自定义注解

    7.1 自定义注解的规则

    自定义注解示例:

    /**
     *自定义注解MyAnnotation
     */
    @Target(ElementType.TYPE) //目标对象是类型
    @Retention(RetentionPolicy.RUNTIME) //保存至运行时
    @Documented //生成javadoc文档时,该注解内容一起生成文档
    @Inherited //该注解被子类继承
    public @interface MyAnnotation {
        public String value() default ""; //当只有一个元素时,建议元素名定义为value(),这样使用时赋值可以省略"value="
        String name() default "devin"; //String
        int age() default 18; //int
        boolean isStudent() default true; //boolean
        String[] alias(); //数组
        enum Color {GREEN, BLUE, RED,} //枚举类型
        Color favoriteColor() default Color.GREEN; //枚举值
    }
    View Code

    自定义注解规则:

    (1)定义注解:使用@interface来声明一个注解,同时将自动继承java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

    (2)配置注解参数(key):注解的每一个方法实际上是声明了一个配置参数。注解方法不带参数,方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。

    (3)注解参数的默认值(value):可以通过default来声明参数的默认值。

    (4)注解参数的可支持数据类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型。

    (5)注解参数的访问权限:只能用public或默认(default)这两个访问权修饰。

    (6)如果只有一个参数成员,建议参数名称设为value()。

    (7)注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或负数作为默认值是一种常用的做法。

    7.2 使用自定义注解

    @MyAnnotation(
            value = "info",
            name = "myname",
            age = 99,
            isStudent = false,
            alias = {"name1", "name2"},
            favoriteColor = MyAnnotation.Color.RED
    )
    public class MyClass {
        //使用MyAnnotation注解,该类生成的javadoc文档包含注解信息如下:
        /*
        @MyAnnotation(value = "info", name = "myname", age = 99, isStudent = false, alias = {"name1","name2"}, favoriteColor = Color.RED)
        public class MyClass
        extends Object
         */
    }
    
    
    public class MySubClass extends MyClass{
        //子类MySubClass继承了父类MyClass的注解
    }
    七、解
    View Code

    八、 解析注解信息

    8.1 AnnotatedElement 接口

    Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。

    通过反射技术来解析自定义注解,关于反射类位于包java.lang.reflect,其中有一个接口 AnnotatedElement,该接口代表程序中可以接受注解的程序元素。

    AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下七个方法来访问Annotation信息:

    返回值方法解释
    T getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回null
    Annotation[] getAnnotations() 返回此元素上存在的所有注解
    Annotation[] getDeclaredAnnotation(Class) 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解;
    Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解;
    Annotation[] getAnnotationsByType(Class) 返回直接存在于此元素上指定注解类型的所有注解;
    boolean  isAnnotationPresent (Class<?extends Annotation> annotationClass) 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false;

    8.1.2 isAnnotationPresent(Class<?extends Annotation>)源码

     public boolean isAnnotationPresent(
            Class<? extends Annotation> annotationClass) {
            if (annotationClass == null)
                throw new NullPointerException();
    
            return getAnnotation(annotationClass) != null;
        }
    View Code

    其实是调用了 getAnnotation(Class )

    8.1.3 getAnnotation(Class )源码

    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
            if (annotationClass == null)
                throw new NullPointerException();
    
            initAnnotationsIfNecessary();//初始化
            return (A) annotations.get(annotationClass);
        }
    View Code

    initAnnotationsIfNecessary()  源码

    //看了下这个方法。基本上就是扫描后放入到annotations中,完成初始话!
     private synchronized void initAnnotationsIfNecessary() {
            clearCachesOnClassRedefinition();//看了下这个方法,好像是清理缓存用的。
            if (annotations != null)
                return;
            declaredAnnotations = AnnotationParser.parseAnnotations(
                getRawAnnotations(), getConstantPool(), this);//这个方法没法找到啊
            Class<?> superClass = getSuperclass();
            if (superClass == null) {
                annotations = declaredAnnotations;
            } else {
                annotations = new HashMap<Class, Annotation>();
                superClass.initAnnotationsIfNecessary();
                for (Map.Entry<Class, Annotation> e : superClass.annotations.entrySet()) {
                    Class annotationClass = e.getKey();
                    if (AnnotationType.getInstance(annotationClass).isInherited())
                        annotations.put(annotationClass, e.getValue());
                }
                annotations.putAll(declaredAnnotations);
            }
        }
    View Code

    8.2 解析注解示例

    (1)FruitName注解

    package com.ray.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.ElementType;
    /**
     * 水果名称注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitName {
        String value() default " ";
    }
    View Code

    (2)FruitColor注解

    package com.ray.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.ElementType;
    /**
     * 水果颜色注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitColor {
        /**
         * 颜色枚举
         */
        public enum Color{BLUE, RED, GREEN};
    
        /**
         * 颜色属性
         * @return
         */
        Color fruitColor() default Color.GREEN;
    }
    View Code

    (3)FruitProvider注解

    package com.ray.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.ElementType;
    /**
     * 水果供应商注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitProvider {
        /**
         * 供应商编号
         * @return
         */
        public int id() default -1;
    
        /**
         * 供应商名称
         * @return
         */
        public String name() default " ";
    
        /**
         * 供应商地址
         * @return
         */
        public String address() default " ";
    }
    View Code

    (4)实体类——Apple类

    package com.ray.annotation;
    
    /***********注解使用***************/
    public class Apple {
        @FruitName("Apple")
        private String appleName;
        
        @FruitColor(fruitColor = FruitColor.Color.RED)
        private String appleColor;
        
        @FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西红富士大厦")
        private String appleProvider;
    
        public String getAppleProvider() {
            return appleProvider;
        }
    
        public void setAppleProvider(String appleProvider) {
            this.appleProvider = appleProvider;
        }
    
        public String getAppleName() {
            return appleName;
        }
    
        public void setAppleName(String appleName) {
            this.appleName = appleName;
        }
    
        public String getAppleColor() {
            return appleColor;
        }
    
        public void setAppleColor(String appleColor) {
            this.appleColor = appleColor;
        }
    
        public void displayName(){
            System.out.println(getAppleName());
        }
    }
    View Code

    (5)注解解析类——AnnotationParser类

    package com.ray.annotation;
    
    import java.lang.reflect.Field;
    
    public class AnnotationParser {
        public static void main(String[] args) throws SecurityException, ClassNotFoundException {
            //1.获取苹果类的属性
            String clazz = "com.ray.annotation.Apple";
            //Field[] fields = AnnotationParser.class.getClassLoader().loadClass(clazz).getDeclaredFields();
            Field[] fields = Class.forName(clazz).getDeclaredFields();
            
            //2.解析注解信息
            for (Field field : fields) {
                //System.out.println(field.getName().toString());
                //2.1当field上标注了FruitName注解时
                if (field.isAnnotationPresent(FruitName.class)){
                    FruitName fruitName = field.getAnnotation(FruitName.class);
                    System.out.println("水果的名称:" + fruitName.value());
    
                    //2.2当field上标注了FruitColor注解时
                }else if (field.isAnnotationPresent(FruitColor.class)){
                    FruitColor fruitColor = field.getAnnotation(FruitColor.class);
                    System.out.println("水果的颜色:"+fruitColor.fruitColor());
                }else if (field.isAnnotationPresent(FruitProvider.class)){
                    FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);
                    System.out.println("水果供应商编号:" + fruitProvider.id() + " 名称:" + fruitProvider.name() + " 地址:" + fruitProvider.address());
                }
            }
        }
    
    }
    View Code

    参考文章:

    1.java注解解析

    2.Java 注解深入理解

    4.Java注解(Annotation)

    5.Java annotation源码解读

    6.聊聊 Java 注解(上)

    7.Java Annotation认知(包括框架图、详细介绍、示例说明)

  • 相关阅读:
    SSL JudgeOnline 1194——最佳乘车
    SSL JudgeOnline 1457——翻币问题
    SSL JudgeOnlie 2324——细胞问题
    SSL JudgeOnline 1456——骑士旅行
    SSL JudgeOnline 1455——电子老鼠闯迷宫
    SSL JudgeOnline 2253——新型计算器
    SSL JudgeOnline 1198——求逆序对数
    SSL JudgeOnline 1099——USACO 1.4 母亲的牛奶
    SSL JudgeOnline 1668——小车载人问题
    SSL JudgeOnline 1089——USACO 1.2 方块转换
  • 原文地址:https://www.cnblogs.com/shirui/p/7562061.html
Copyright © 2011-2022 走看看