简单的Java注解机制

一、什么是注解

       注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解

一般常用的注解可以分为三类:

  • 一类是Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查
  • 一类为元注解,元注解是用于定义注解的注解,包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)
  • 一类为自定义注解,可以根据自己的需求定义注解

二、注解的用途

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例

三、注解的使用

1、Java自带的标准注解

常见标准的Annotation:

1.)Override
        java.lang.Override是一个标记类型注解,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。

2.)Deprecated
       Deprecated也是一种标记类型注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。

3.)SuppressWarnings
       SuppressWarning不是一个标记类型注解。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,被-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。
  @SuppressWarnings("unchecked")

这种注解的使用非常简单,只需在需要注解的地方标明某个注解即可,例如在方法上注解:

public class Test {
    @Override
    public String tostring() {
        return "override it";
    }
}

例如在类上注解:

@Deprecated
public class Test {

}

2、元注解

java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
              @Documented
              @Retention
              @Target
              @Inherited

注解 作用
@Target 表示该注解用于什么地方。如果不明确指出,该注解可以放在任何地方

ElementType.TYPE : 用于描述类、接口或enum声明
ElementType.FIELD : 成员变量、对象、属性(包括enum实例)
ElementType.METHOD : 用于描述方法
Element.PARAMETER : 用于描述参数
Element.CONSTRUCTOR : 用于描述构造器
Element.LOCAL_VARIABLE : 用于描述局部变量
ElementType.ANNOTATION_TYPE : 用于描述类、接口(包括注解类型) 或enum声明
ElementType.PACKAGE : 用于描述包
@Retention 定义该注解的生命周期

RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码(@Override, @SuppressWarnings都属于这类注解)
RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式
@Document 表示是否将注解信息添加在java文档中
@Inherited 定义该注释和子类的关系

注:如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation

3、自定义注解

自定义注解类编写的一些规则:

  1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用public或默认(default)这两个访问权修饰
  3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
  5. 注解也可以没有定义成员, 不过这样注解就没啥用了
    PS:自定义注解需要使用到元注解
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface Test {
   
 }

       在注解中一般会有一些元素以表示某些值。注解的元素看起来就像接口的方法,唯一的区别在于可以为其制定默认值。没有元素的注解称为标记注解,上面的@Test就是一个标记注解。
       注解的可用的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。而且元素不能使用null作为默认值。注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。

四、反射机制与注解机制

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

方法 含义
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null
Annotation[] getAnnotations() 返回该程序元素上存在的所有注解。
boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注解。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
default <T extends Annotation> T[] getAnnotationsByType(Class <T> annotationClass) 返回该元素制定类型的注解
default <T extends Annotation> T getDeclaredAnnotation( Class <T> annotationClass) 返回直接存在与该元素上的所有注解
default <T extends Annotation>T[] getDeclaredAnntationsByType(Class <T> annotationClass) 返回直接存在该元素上某类型的注解

接下来让我们在代码中看看二者的结合

//定义一个可以注解在Class,interface,enum上的注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetType {
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "MyAnTargetType我是定义在类接口枚举类上的注解元素value的默认值";
}
//定义一个可以注解在PARAMETER上的注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetParameter {
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "MyAnTargetParameter我是定义在参数上的注解元素value的默认值";
}
// 定义一个可以注解在METHOD上的注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetMethod {
    /**
     * 定义注解的一个元素 并给定默认值
     *
     * @return
     */
    String value() default "MyAnTargetMethod我是定义在方法上的注解元素value的默认值";
}
// 定义一个可以注解在FIELD上的注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetField {
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "MyAnTargetField我是定义在字段上的注解元素value的默认值";
}
//创建一个使用了以上四种注解的类

@MyAnTargetType
public class AnnotationTest {
    @MyAnTargetField
    private String field = "我是字段";

    @MyAnTargetMethod("测试方法")
    public void test(@MyAnTargetParameter String args) {
        System.out.println("参数值 === " + args);
    }
}
public class Main{

    public static void main(String[] args) {
        // 获取类上的注解MyAnTargetType
        MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
        System.out.println("类上的注解值 === " + t.value());
        MyAnTargetMethod tm = null;
        try {
            // 根据反射获取AnnotationTest类上的test方法
            Method method = AnnotationTest.class.getDeclaredMethod("test", String.class);
            // 获取方法上的注解MyAnTargetMethod
            tm = method.getAnnotation(MyAnTargetMethod.class);
            System.out.println("方法上的注解值 === " + tm.value());
            // 获取方法上的所有参数注解  循环所有注解找到MyAnTargetParameter注解
            Annotation[][] annotations = method.getParameterAnnotations();

            for (Annotation[] tt : annotations) {
                for (Annotation t1 : tt) {
                    if (t1 instanceof MyAnTargetParameter) {
                        System.out.println("参数上的注解值 === " + ((MyAnTargetParameter) t1).value());
                    }
                }
            }

            method.invoke(new AnnotationTest(), "改变默认参数");
            // 获取AnnotationTest类上字段field的注解MyAnTargetField
            MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);
            System.out.println("字段上的注解值 === " + fieldAn.value());
        } catch (Exception e) {
            e.printStackTrace();
        }

运行结果如下:

类上的注解值 === MyAnTargetType我是定义在类接口枚举类上的注解元素value的默认值
方法上的注解值 === 测试方法
参数上的注解值 === MyAnTargetParameter我是定义在参数上的注解元素value的默认值
参数值 === 改变默认参数
字段上的注解值 === MyAnTargetField我是定义在字段上的注解元素value的默认值

最后再写一个关于@Inherited例子:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnTargetType {
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "MyAnTargetType我是定义在类接口枚举类上的注解元素value的默认值";
}
public class Main extends AnnotationTest{

    public static void main(String[] args) {
        MyAnTargetType t2 = Main.class.getAnnotation(MyAnTargetType.class);
        System.out.println("类上的注解值 === "+t2.value());
    }
}

运行如下:

类上的注解值 === 我是定义在类接口枚举类上的注解元素value的默认值

说明已经获取到了父类AnnotationTest的注解了
如果MyAnTargetType去掉@Inherited注解运行则报错如下:

Exception in thread "main" java.lang.NullPointerException
    at com.company.Main.main(Main.java:41)

最后借用下别人的Java注解的基础知识点导图


5713484-e2afe3f5096f32e9.png

关于Java注解机制的基础就讲完了,欢迎大家前来diss

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和...
    九尾喵的薛定谔阅读 3,257评论 0 2
  • 对于java中的思考的方向,1必须要看前端的页面,对于前端的页面基本的逻辑,如果能理解最好,不理解也要知道几点。 ...
    神尤鲁道夫阅读 843评论 0 0
  • 本文章涉及代码已放到github上annotation-study 1.Annotation为何而来 What:A...
    zlcook阅读 29,351评论 15 116
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 7,134评论 4 31
  • 51.养成自己查看各项政策通知更新的习惯,拓宽自己的信息渠道。 52.合理规划自己的支出收入,做一个有经济计划的人...
    我叫何奕洺阅读 183评论 0 0