什么是注解

注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。《Thinking in Java》

怎么理解呢,注解好比一个职业的名称,比如老师这个名称类似一个注解,当我们听到老师这个职称,我们就知道他会教书育人;他是一个律师,我们的第一反应就是他很懂法律知识,注解类似一个标签,他告诉我们这个方法或者类有什么特征之类的,使我们更清楚的了解类或方法。
我们常见的注解,@Override表示这个方法重写了父类的方法;@Deprecated表示这个方法或者类过时了,不建议使用;@Test表示这个方法是测试方法。

如何定义注解

注解和类,接口一样,需要关键字@interface来定义。是的你没看错,和定义接口的interface差不多,只是在interface前面加了个@。

  1. public @interface MyAnnotation {//定义注解
  2. }

image.png

  • 我们查看这个注解的字节码文件,发现定义的注解实际上实现了Annotation接口。

    1. public interface Annotation {//Annotation接口源码
    2. boolean equals(Object obj);
    3. int hashCode();
    4. String toString();
    5. /**
    6. *获取注解类型
    7. */
    8. Class<? extends Annotation> annotationType();
    9. }
  • 那我们能不用@interface关键字,自己写一个接口继承Annotation接口呢,答案是可以的 ```java public interface MyAnnotation2 extends Annotation {

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2439492/1607353072278-408db576-c8aa-47cf-868b-57a54d371db5.png#align=left&display=inline&height=140&margin=%5Bobject%20Object%5D&name=image.png&originHeight=140&originWidth=756&size=17145&status=done&style=none&width=756)<br />发现编译后的字节码文件 一个是@interface定义的,一个是interface定义的,其他事一模一样的。
  2. - 当我们在使用的时候,发现直接用接口继承Annotation接口的MyAnnotation2是无法用@在方法上使用的,编译会报错。
  3. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2439492/1607353357953-5cec78ac-0f2c-4309-af00-d13aa6a96f72.png#align=left&display=inline&height=171&margin=%5Bobject%20Object%5D&name=image.png&originHeight=171&originWidth=405&size=13182&status=done&style=none&width=405)<br />**所以我们自定义注解的时候,肯定是@interface来定义注解。**<br />当我们定义一个注解的时候,里面并没有写任何的代码(即不含任何元素),这个注解就是个标记注解,如@Test,@Override等等。
  4. <a name="mQBHP"></a>
  5. ### 元注解
  6. 元注解其实就是**注解的注解**,在注解中使用元注解,更好的帮我们开发想要的功能代码。(PS 定义注解的属性用括号结尾)<br />以JDK1.8为例,有五种元注解,如下:
  7. - **@Target**
  8. 表示注解的可以用于什么地方,可选参数是枚举ElementType中的属性
  9. ```java
  10. public enum ElementType {
  11. TYPE, //类,接口,枚举,注解的声明
  12. FIELD,//域(属性字段或枚举常量)的声明
  13. METHOD,//方法的声明
  14. PARAMETER,//方法参数的声明
  15. CONSTRUCTOR,//构造函数的声明
  16. LOCAL_VARIABLE,//局部变量的声明
  17. ANNOTATION_TYPE,//注解的声明
  18. PACKAGE,//包的声明
  19. TYPE_PARAMETER,//泛型的声明
  20. TYPE_USE//此类型包括类型声明和类型参数声明
  21. }

例子:

  1. @Target(ElementType.CONSTRUCTOR)
  2. public @interface MyAnnotation {//表示该注解作用于构造函数
  3. }

一般使用的最多的是@Target(ElementType.TYPE)

  • @Retention

表示注解的保留方式,可选参数是枚举RetentionPolicy中的属性

  1. public enum RetentionPolicy {
  2. SOURCE,//表示注解会在编译时被丢弃
  3. CLASS,//默认策略,表示注解在class文件中可用,但是在运行时,不会被VM保留
  4. RUNTIME//表示不仅会在编译后的class文件中存在,而且在运行时保留,因此它们主要用于反射场景,可以通过getAnnotation方法获取注解信息
  5. }

例子:
首先定义三种保留方式的注解

  1. //存在于class文件中,会被VM丢弃
  2. @Target(ElementType.TYPE)
  3. @Retention(RetentionPolicy.CLASS)
  4. public @interface MyAnnotation {
  5. String str();
  6. }
  7. //在运行时保留
  8. @Target(ElementType.TYPE)
  9. @Retention(RetentionPolicy.RUNTIME)
  10. public @interface MyAnnotation2 {
  11. String str();
  12. }
  13. //在编译时被丢弃
  14. @Target(ElementType.TYPE)
  15. @Retention(RetentionPolicy.SOURCE)
  16. public @interface MyAnnotation3 {
  17. String str();
  18. }

接下来写下测试方法,看结果

  1. @MyAnnotation(str="1")
  2. @MyAnnotation2(str="2")
  3. @MyAnnotation3(str="3")
  4. public class test {
  5. public static void main(String[] args) {
  6. Annotation[] annotations = test.class.getAnnotations();
  7. for (Annotation annotation :annotations){
  8. System.out.println("保留的注解:"+annotation.toString());
  9. }
  10. //输出: 保留的注解:@demo.a5.MyAnnotation2(str=2)
  11. }
  12. }

使用反射,我们可以得到运行时的注解属性,从输出结果看到只有@MyAnnotation2注解的属性输出了,因为它的元注解@Retention参数是RetentionPolicy.RUNTIME

  • @Documented

此注解表示,将修饰的注解包含在Javadoc中,Javadoc工具会将此注解标记元素的注解信息包含在javadoc中。默认,注解信息不会包含在Javadoc中

  • @Inherited

此注解表示,修饰的注解允许子类继承父类(PS:此注解只对注解标记的超类有效,对接口是无效的。)
@Inherited注解标记的注解,在使用时,如果父类和子类都使用的注解是同一个,那么子类的注解会覆盖父类的注解
例子:

  1. @Inherited
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.TYPE)
  4. public @interface MyAnnotation {
  5. String str();
  6. }
  7. @MyAnnotation(str = "222")
  8. public class Father {
  9. }
  10. public class Son extends Father {
  11. }
  12. public class demo {
  13. public static void main(String[] args){
  14. son son = new son();
  15. Annotation[] annotations = son.class.getAnnotations();
  16. for (Annotation annotation :annotations){
  17. System.out.println("保留的注解:"+annotation.toString());
  18. }
  19. //输出:保留的注解:@demo.a5.mytest(str=222)
  20. }
  21. }

从结果可以看出,子类能获取到父类注解的属性。

  • @Repeatable(JDK1.8加入)

此注解表示,标记的注解可以多次应用于相同的声明或类型
例子:

  1. @Repeatable(MyAnnotation2.class)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.TYPE)
  4. public @interface MyAnnotation{
  5. String value() default "";
  6. }
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Target(ElementType.TYPE)
  9. public @interface MyAnnotation2{
  10. MyAnnotation[] value();
  11. }
  12. @MyAnnotation(value="1")
  13. @MyAnnotation(value="11")
  14. @MyAnnotation(value="111")
  15. public class test {
  16. public static void main(String[] args) {
  17. Annotation[] annotations = test.class.getAnnotations();
  18. for (Annotation annotation :annotations){
  19. System.out.println("保留的注解:"+annotation.toString());
  20. }
  21. //输出:保留的注解:@demo.a5.MyAnnotation2(value=[@demo.a5.MyAnnotation(value=1), @demo.a5.MyAnnotation(value=11), @demo.a5.MyAnnotation(value=111)])
  22. }
  23. }

从结果可以看到,注解中的1,11,111都输出了,表示该注解可以多次应用于相同的声明或类型

自定义注解的使用

image.png
因为元注解@Retention(RetentionPolicy.RUNTIME)使我们能够用反射的方法,拿到注解的属性值,这样我们可以利用注解做很多事情,贯穿到我们的业务中去。
我们看以下例子,具体了解,怎么通过反射获取的。

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface People {//定义注解
  4. String name() default "";
  5. int age() default 10;
  6. }
  7. public class Student {//定义Student类
  8. private String info;
  9. public String getInfo() {
  10. return info;
  11. }
  12. public void setInfo(String info) {
  13. this.info = info;
  14. }
  15. @Override
  16. @People(name="小张", age = 18)
  17. public String toString() {
  18. return "Student{" +
  19. "info='" + info + '\'' +
  20. '}';
  21. }
  22. }
  23. //编写测试类
  24. public class test {
  25. public static void main(String[] args) {
  26. getStudentInfo(Student.class);//参数为Studen类的class对象
  27. }
  28. public static void getStudentInfo(Class<?> clazz){
  29. Method[] methods = clazz.getMethods();//通过反射获取Student类的所有方法
  30. for (Method method : methods) {//便利所有方法
  31. if (method.isAnnotationPresent(People.class)) {//判断注解是否为People注解
  32. People annotation = method.getAnnotation(People.class);//获取该方法上的注解
  33. System.out.println("姓名:"+annotation.name() + ",年龄:"+ annotation.age());//输出该注解的属性信息
  34. }
  35. }
  36. }
  37. }

image.png

注解的作用

  • 灵活使用注解,穿插到我们的业务代码中去,能够大大的提高我们的开发效率
  • 利用注解,帮我们生成Javadoc文档
  • 利用注解类型检测能力,在代码编译前帮我们排错