Java注解

1.Java 注解的描述

java 注解又叫做 java 标注,是 java 的一种注释机制,在 jdk5.0 引入。其可以作用在类、方法、变量、参数和包上。另外,其可以通过反射来获取注解标注的内容。可以说注解就是实现了 Annotation 的接口

2.Java 注解的分类

Java 注解分类的话,大致可以分为三类,分别是标准注解,元注解与自定义注解。

  • 标准注解
    标准注解就是 java 内置的注解,主要有 @Override,@Deprecated,@SuppressWarnings,@FunctionalInterface
    • @Override:这个注解的作用主要是检查该注解标注的方法是否是重写方法,如果不是重写方法的话编译会不通过会报错。

    • @Deprecated:这个注解的作用主要是标记该方法可能要废弃了,使用的话会报个警告。

    • @SuppressWarnings:这个注解主要是告诉编译器要忽视一些警告。其常见的参数有:

      参数 作用
      deprecation 使用了不赞成使用的类或方法的警告
      unchecked 执行了未检查的转换时的警告,例如使用集合时没有用泛型来指定集合保存的类型
      fallthrough 当 switch 程序块两个 case 之间没有 break 时
      path 在类路径,源文件路径中有不存在的路径时的警告
      serial 在可序列化的类上缺少 serialVersionUID 定义时的警告
      all 所有的警告
    • @FunctionalInterface:用于指示被修饰的接口是函数式接口,在 jdk8 被引入

  • 元注解
    主要有 @Retention,@Documented,@Target,@Inherited,@Repeatable
    • @Retention:用来定义该注解在哪个级别可用,也就是注解的生命周期,一个注解只能定义一个级别。具体有三个级别:
      image

      参数 描述
      SOURCE 在编译时被丢弃,不包含在类文件中,既不会参与编译也不会在运行时起到什么作用
      CLASS JVM 加载时被丢弃,包含在类文件中,但是不能在运行时被获取到,默认值
      RUNTIME 由 JVM 加载包含在类文件中,能在运行时被获取到,一般开发自定义注解使用该级别
    • @Documented:表示该注解能不能出现在 javadoc 中,被该元注解修饰的注解表示能出现在 javadoc 中,如果没有被该元注解修饰,则是不会出现在 javadoc 中

    • @Target:用来表示该注解的适用范围,一个注解可以有多个适用范围,每个使用范围之间在大括号内用逗号隔开。如:
      @Target({ElementType.TYPE, ElementType.METHOD})
      使用范围的值总共有十个:
      image

      参数 描述
      TYPE 应用于类、接口 (包括注解类型) 或枚举
      FIELD 应用于字段 (包括枚举常量)
      METHOD 应用于方法
      PARAMETER 应用于形参,也就是方法的参数
      CONSTRUCTOR 应用于构造函数
      LOCAL_VARIABLE 应用于局部变量
      ANNOTATION_TYPE 应用于注解
      PACKAGE 应用于包
      TYPE_PARAMETER 应用于类型参数,JDK1.8 引入
      TYPE_USE 应用于任何类型,JDK1.8 引入
    • @Inherited:该注解说明子类可以继承父类的注解,也就是说,如果被该元注解修饰的注解,能够在子类继承父类的时候把注解也继承过去。但是这边有一个需要注意的点,就是被该元注解修饰的注解只有应用在类上的时候才能够被继承过去,应用在方法或其他地方时是没有的,这边我刚开始就是用在了方法上,然后一直没效果。例子:

      注解:
      package com.mcj.music.annotation;
      

      import java.lang.annotation.*;

      /**

      • @author mcj

      • @date 2022/10/28 20:27

      • @description 日志有关的自定义注解
        */
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.TYPE, ElementType.METHOD})
        @Inherited
        public @interface LogAnnotation {

        String value() default "";

      }

      父类:
      package com.mcj.music.annotation;

      /**

      • @author mcj
      • @date 2022/10/29 9:40
      • @description
        */
        @LogAnnotation("测试 inherited 注解")
        public class TestInherited {
        }

      子类:
      package com.mcj.music.annotation;

      /**

      • @author mcj

      • @date 2022/10/29 9:40

      • @description
        */
        public class ChildTestInherited extends TestInherited{

        public static void main(String[] args) {
        System.out.println(TestInherited.class.isAnnotationPresent(LogAnnotation.class));
        System.out.println(ChildTestInherited.class.isAnnotationPresent(LogAnnotation.class));
        }

      }

      输出结果:
      image
      去掉 @Inherited 注解的结果:
      image
      image

    • @Repeatable:表示被该元注解修饰的注解能够重复使用,也就是可以在一个方法上使用多次该注解,可以说将该注解用一个数组容器包了起来。例子:

      //LogAnnotation 注解
      package com.mcj.music.annotation;
      

      import java.lang.annotation.*;

      /**

      • @author mcj

      • @date 2022/10/28 20:27

      • @description 日志有关的自定义注解
        */
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.TYPE, ElementType.METHOD})
        @Repeatable(LogAnnotations.class)
        public @interface LogAnnotation {

        String value() default "";

      }

      // LogAnnotations 注解
      package com.mcj.music.annotation;

      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;

      /**

      • @author mcj
      • @date 2022/10/29 9:52
      • @description
        */
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.TYPE, ElementType.METHOD})
        public @interface LogAnnotations {
        LogAnnotation[] value();
        }

      // 测试类:
      package com.mcj.music.annotation;

      import java.lang.annotation.Annotation;
      import java.lang.reflect.Array;
      import java.util.Arrays;

      /**

      • @author mcj

      • @date 2022/10/29 9:55

      • @description
        */
        @LogAnnotation("测试 repeatable1")
        @LogAnnotation("测试 repeatable2")
        public class TestRepeatable {

        public static void main(String[] args) {
        Annotation[] annotations = TestRepeatable.class.getAnnotations();
        System.out.println(annotations.length);
        System.out.println(Arrays.toString(annotations));
        }

      }

      运行结果:
      image
      可以从结果看出,虽然注解的长度是 1,但是它里面有两个不同的值
      PS:需要注意的是 @Repeatable 注解声明的注解 (也就是 LogAnnotation) 要比 @Repeatable 值中的注解 (也就是 LogAnnotations) 的作用范围大或相等,可用级别小或相等。

  • 自定义注解
    自定义注解大致可以分为三步:1. 定义注解,2. 配置注解,3. 解析注解
    • 1. 定义注解 - 也就是声明一个注解,创建一个注解对象
      注解与方法、变量、枚举等声明方式相似,唯一不同的是其关键字为 @interface,也就是只要是声明时使用了该关键字,底层在实现的时候都会继承 java.lang.annotation.Annotation 接口。
      public @interface LogAnnotation {
      

      }


      在注解的方法体里面只能写一个东西:注解类型元素,也就是注解的属性。其格式为:
      修饰符 数据类型 属性名() default 默认值;
      其中修饰符必须是 public,所以可以直接不写,其默认值即是 public
      其中可以写的数据类型有:八种基本类型 (int,short,long,double,byte,char,boolean,float),String,Class,注解类型,枚举类以及上面任一种类型的数组形式。
      其中的属性名是自定义的
      其中的 () 并不是定义参数的地方,只是一种语法而已
      default 表示该属性的默认值,其默认值与前面的数据类型一致,有个要注意的点是如果没有设置默认值,则该注解在使用的时候一定要赋值。如下:
      public @interface LogAnnotation {

      <span class="hljs-comment">// 值描述</span>
      String <span class="hljs-built_in">value</span>() default "";
      
      <span class="hljs-comment">// 名字</span>
      String <span class="hljs-built_in">name</span>();
      

      }

    • 2. 配置注解 - 也就是对上面定义的注解配置其作用范围及生命周期
      配置注解则是利用上面记录的几种元注解对自定义的注解进行修饰,来描述自定义注解的生命周期与作用范围。如:
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface LogAnnotation {
      
      <span class="hljs-comment">// 值描述</span>
      String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;
      
      <span class="hljs-comment">// 名字</span>
      String <span class="hljs-title function_">name</span><span class="hljs-params">()</span>;
      

      }

    • 3. 解析注解 - 也就是在程序运行时检测注解,并进行一系列操作
      注解的解析主要是通过 java 的反射来实现的,可以通过反射来获取相应的类、方法或字段,通过获取的类、方法、字段来判断其上是否使用了该注解,使用的话则获取该注解与该注解内各个元素的值。例子如下:
      package com.mcj.music.annotation;
      

      import java.lang.reflect.Method;

      /**

      • @author mcj

      • @date 2022/10/29 14:26

      • @description
        */
        public class TestAnnotation {

        @LogAnnotation(name = "mcj", value = "10.09 测试")
        public void printString() {
        System.out.println("输出一个字符串");
        }

        public static void main(String[] args) {
        Class<TestAnnotation> testAnnotationClass = TestAnnotation.class;
        Method[] methods = testAnnotationClass.getMethods();
        for (Method method : methods) {
        if (method.isAnnotationPresent(LogAnnotation.class)) {
        LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
        String name = annotation.name();
        String value = annotation.value();
        System.out.println(String.format("方法 %s 用了 LogAnnotation 注解,注解值 name 为:%s,value 为:%s", method.getName(), name, value));
        }
        }
        }

      }


      输出结果:
      image
      描述:方法中的 TestAnnotation.class 就是利用反射获取 TestAnnotation 这个类对象,testAnnotationClass.getMethods()这个则是根据类对象获取该类中所有的方法,method.isAnnotationPresent(LogAnnotation.class) 则是判断方法是否存在 LogAnnotation 注解,需要注意的是要看注解是标注再什么上面,如果是类上面则是类.isAnnotationPresent()如果是字段则是字段.isAnnotationPresent()这里是标注在方法上的。最后的 getAnnotation() 则是获取这个注解,然后利用使用枚举值的方式就可以获取注解中的属性值了。

3. 总结

java 的注解主要是对我们的开发起到一个辅助作用,帮助我们更容易的进行开发。其中自定义注解,主要是的就是那五个元注解,不过经常用的也就 @Retention 和 @Target 这两个元注解,另外对于注解的解析则是利用了 java 的反射机制。