注解是什么

转载自https://www.zybuluo.com/xiaohaizi/note/965272

上集回顾

上集中唠叨了只能创建有限个对象的枚举类型,用enum声明,直接填写对象名便可以使用,设计java的大叔们为了我们的方便把能省略的尽量都给省略了,赶紧感谢他们吧~

元数据

这个世界除了真实数据之外,还有好多针对真实数据描述数据。比如对于一个字符串"你真蠢"我们可以从不同的角度去描述它:

  • 这个字符串有3个字符 (字符数量角度)
  • 这个字符串是中文的 (语言角度)
  • 这个字符串是骂人的话 (用途角度)
  • 这个字符串是王庆峰说的 (说话者角度)

对于真实数据"你真蠢"来说,上述从不同角度来描述这个真实数据的数据,比如说"字符数量是3""语言是中文"等这些描述数据,我们也称为元数据,英文名叫metadata,为了把这些描述的数据也记下来,我们通常可以定义一个文件,文件里写成这样:

 
 
  1. 字符数量:3
  2. 语言:中文
  3. 用途:骂人
  4. 说话者:王庆峰

这些描述数据,也就是元数据是跟真实数据没关系的,每个人都可以从他关心的角度去编写真实数据的描述数据,所以你也可以给"你真蠢"这个真实数据定义自己关心的一些元数据,加在自己的描述文件里试试哈~

对java代码的各个部分的描述

除了字符串,世间的所有东西都可以用来描述,来产生对应的描述数据,也就是所谓的元数据。我们当然也可以描述我们的java代码了。我们知道我们写的java代码是由好多部分组成的,比如:包名类名字段方法啥的。比如我们定义一个Empolyee类:

 
 
  1. public class Empolyee {
  2. private String name; //姓名
  3. private int age; //年龄
  4. private String id; //身份证号
  5. private float salary; //工资,单位:元
  6. public Person(String name, int age, String id, float salary) {
  7. this.name = name;
  8. this.age = age;
  9. this.id = id;
  10. this.salary = salary;
  11. }
  12. // ... 为节省篇幅,省略字段的getter/setter方法
  13. public void laugh() {
  14. System.out.println("laughing");
  15. }
  16. public void cry() {
  17. System.out.println("crying");
  18. }
  19. public void run() {
  20. System.out.println("running");
  21. }
  22. public void walk() {
  23. System.out.println("standing");
  24. }
  25. }

我们可以对这个代码的各个部分添加描述,把这些描述存储到单独的文件里去,这些描述并不会对代码的运行产生一毛钱的影响。比如我们对laugh方法进行描述,可以写个这样的描述文件:

 
 
  1. 是否是面部表情:是
  2. 是否锻炼身体:否
  3. 累不累:不累

如果我们乐意,我们可以对这个Empolyee类的各个部分都可以从我们关心的角度添加一些描述数据。但是话说回来,我们添加了一堆描述数据,它们又不会影响程序的运行,我们费这工夫有啥用啊?

哈哈,这些描述文件的存在并不是去影响程序执行过程的,而是为了方便我们对程序结构的分析。这些描述都是从我们自己角度去添加的,你想啊,如果有一天你想知道这个类里有哪些是有关面部表情的方法,你就可以直接查看这些描述文件就好了。所以说,如果我们并不想分析这些描述文件,那它们便没一分钱卵用~

从我自己的角度来说,现在我想给Empolyee类中的字段设计一个Excel表格,但是我们知道每个Excel单元格都有格式,我想让有些字段有一些特殊格式,所以我给Empolyee中的各个字段加了一些描述:

 
 
  1. `name`字段对应`数值`的单元格格式,居中加粗,14号字体;
  2. `age`字段对应`数值`的单元格格式;
  3. `id`字段对应`文本`的单元格格式;
  4. `salary`字段对应`货币`的单元格格式,加粗;

有了这个描述文件,后边即使不是我亲自去把各个字段转为Excel表格,随便换一个人,只要他能看懂这个描述文件,他就可以针对这个描述文件进行单元格格式设置。

但是单独定义描述文件的话是有不好的地方的,拿这个生成Excel表格的描述文件为例,如果我们之后修改了java的源代码,比如加了个籍贯的字段,那我们也需要在对应的描述文件中添加一下该字段的描述;或者有时候我们一时迷糊,java源代码中删除掉了一个字段,但是描述文件中该字段还存在。这都是因为我们要维护两个地方的害处,所以设计java的大叔们设计了一种可以直接在java源代码中添加对代码的各个部分进行描述的方式 --- 注解

注解

这个注解的定义使用方式很简单:

  1. 先定义一个注解

    我们需要用@interface的方式去声明注解,比如我们想声明一个单元格格式的注解,这么写就好了:

     
       
    1. public @interface CellFormat {
    2. }

    这样,CellFormat就表示一个单元格格式的注解了,但是格式是有具体内容,比如是否是黑体,字体大小,对齐方式啥的,所以需要写成这样:

     
       
    1. public @interface CellFormat {
    2. String format();
    3. boolean isBold() default false;
    4. int fontSize() default 12;
    5. String align() default "left";
    6. }

    就像是在接口中声明方法一样,这些声明的方法也称为注解元素,这些元素就代表着单元格格式的具体内容。大家看到,有的方法后边加了default,意思就是这个方法的默认值是什么,比如isBond方法的默认值就是falsefontSize的默认值就是12,这些默认值有啥用,我们稍后再看。

  2. 在需要描述的代码上加@+注解名

    比如我们想描述Employee类的各个字段对应的Excel表格格式,我们就可以这么写:

     
       
    1. public class Employee {
    2. @CellFormat(format = "数值", isBold = true, fontSize = 14, align = "center")
    3. private String name; //姓名
    4. @CellFormat(format = "数值")
    5. private int age; //年龄
    6. @CellFormat(format = "文本")
    7. private String id; //身份证号
    8. @CellFormat(format = "货币", isBold = true)
    9. private float salary; //工资,单位:元
    10. // ... 为节省篇幅,省略其他方法
    11. }

    大家看到了,使用注解的方式就是@+注解名的方式加到我们所描述的代码上,然后在后边加上小括号(),在小括号里填上对应的方法返回的值。如果某个方法没有设置默认值的话,必须声明其返回值,但是如果某个方法有默认值并且没有手动赋值的话,就采用默认值

当然,我们定义的注解里也可以没有方法,比如我们定义一个表示是面部表情的注解:

 
 
  1. public @interface FacialExpression {
  2. }

然后用它去描述方法的时候,就不用在小括号()里添加各个方法的返回值了:

 
 
  1. public class Employee {
  2. // ... 为节省篇幅,省略其他字段和方法
  3. @FacialExpression
  4. public void laugh() {
  5. System.out.println("laughing");
  6. }
  7. @FacialExpression
  8. public void cry() {
  9. System.out.println("crying");
  10. }
  11. }

定义并使用一个注解就是这么简单,我们再强调一遍:注解的使用对程序的运行没有一毛钱关系,它只是对代码的一种描述,和把这些描述写在单独的文件里没有什么区别,也就是说注解就是代码的一种元数据

注解本质

我们把我们写的CellFormat.java文件编译一下,发现出现了一个CellFormat.class文件,也就是说这个所谓的注解变成了一个类,如果我们用javap -c CellFormat.class再查看一下这个类的具体内容的话,我们会得到下边的代码:

 
 
  1. public interface CellFormat extends java.lang.annotation.Annotation {
  2. public abstract java.lang.String format();
  3. public abstract boolean isBold();
  4. public abstract int fontSize();
  5. public abstract java.lang.String align();
  6. }

也就是说,其实我们定义了一个叫CellFormat的接口,它继承自java.lang.annotation包中的Annotation接口。

哈哈,那我们用一个普通的接口去试试看看行不:

 
 
  1. interface II {}
  2. public class Employee {
  3. // ... 为节省篇幅,省略其他字段和方法
  4. @II //编译错误❌
  5. public void run() {
  6. System.out.println("running");
  7. }
  8. }

哦,发现了编译错误❌,只有用@interface声明的注解才可以用来描述代码

注意事项

注解里的方法可不是随便声明的,是有规定的:

  1. 必须是public的,如果不填访问权限则默认是public的。

    它本质就是个接口嘛!

  2. 方法返回类型必须是下边的这些类型:

    • 所有基本数据类型(intfloatbooleanbytedoublecharlongshort)
    • String类型
    • Class类型
    • enum类型
    • Annotation类型,它是所有注解的祖宗接口
    • 以上所有类型的数组

    为啥会有这个强制规定,我现在还不是很明白,但是它就是规定,你不遵守就让你的代码编译不过,你能咋滴!所以如果我们在定义CellFormat注解的时候加了这个方法:

     
       
    1. public @interface CellFormat {
    2. Employee getEmployee(); //编译错误❌
    3. }

    因为getEmployee方法把我们的自定义类型Employee作为返回类型,就编译错误喽~

  3. 如果注解的方法名是value,并且它是唯一的一个需要赋值的注解元素的时候,则可以在使用时在小括号()中直接填写值。

    这个特点比较有意思,比如我们定义一个注解:

     
       
    1. public @interface ValueAnnotation {
    2. String value(); //方法名叫做value
    3. int otherMethod() default 5;
    4. }

    那我们在使用这个注解的时候,可以直接在小括号()里指定值:

     
       
    1. public class Test {
    2. @ValueAnnotation("abc")
    3. public void method();
    4. }

    @ValueAnnotation("abc")的意思和@ValueAnnotation(value = "abc")是一样的!!!

    千万要注意的是,只有在方法名是value并且它是唯一的一个需要赋值的注解元素的时候才能这么用。如果我们也要给otherMethod赋值的话,那就必须显式的写出来,就像这样:

     
       
    1. public class Test {
    2. @ValueAnnotation(value = "abc", otherMethod = 6)
    3. public void method();
    4. }

元注解

我们上边定义的CellFormat注解可以用,但是有个问题,就是这个注解可以加在代码的任何部分:

 
 
  1. @CellFormat //加在了类名上头
  2. public class Action {
  3. @CellFormat //加在了方法上头
  4. @FacialExpression
  5. public void laugh() {
  6. System.out.println("laughing");
  7. }
  8. // ... 为节省篇幅,省略其他代码
  9. }

这就不好了,我们只想把它加在需要转成Excel表格的字段上呀,所以我们需要规定一下这个注解的使用范围。设计java的大叔们想到了这个问题,并且还想到了别的问题,所以他们提供了4种描述注解的注解,所以也叫元注解。我们可以在自定义注解的时候用他们提供的这些元注解来注解我们的自定义注解,哈哈,有点儿扰,其实就像是这样:

 
 
  1. @元注解1
  2. @元注解2
  3. ...
  4. public @interface 自定义注解名 {
  5. 注解元素1
  6. 注解元素2
  7. ...
  8. }

这些元注解是java语言自带的,我们看看有哪些,分别有啥用:

  1. @Target

    这个元注解中只有一个方法:

     
       
    1. ElementType[] value();

    ElementType是一个枚举类型,每个枚举对象代表了java代码的某个部分,我们需要了解下边这几种枚举对象:

    • ElementType.CONSTRUCTOR:用于描述构造方法
    • ElementType.FIELD:用于描述字段
    • ElementType.LOCAL_VARIABLE:用于描述方法体中的局部变量
    • ElementType.METHOD:用于描述方法
    • ElementType.PACKAGE:用于描述包
    • ElementType.PARAMETER:用于描述方法参数
    • ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
    • ElementType.ANNOTATION_TYPE:用于描述注解

    因为Target中只有一个value方法,所以我们可以在@Target后的小括号()里直接填上注解元素的值,又由于value方法的返回值是一个数组,所以需要使用中括号把这些枚举对象扩起来。比如我们想让某个自定义注解应用在构造方法方法字段上,我们就可以在自定义注解上边加上:

     
       
    1. @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})

    而对于我们前边定义的CellFormat注解来说,我们只想让它用在字段上,所以可以这么写:

     
       
    1. ElementTypeTarget都定义在`java.lang.annotation`包里,所以使用的时候需要import一下
     
       
    1. import java.lang.annotation.ElementType;
    2. import java.lang.annotation.Target;
    3. @Target({ElementType.FIELD})
    4. public @interface CellFormat {}

    因为大括号{}里边只有一个元素,在这种情况下我们也可以把大括号{}省略掉,写成这样:

     
       
    1. @Target(ElementType.METHOD)
    2. public @interface FacialExpression {}

    此时如果我们再在非字段的地方上加上这个注解,就会编译报错了:

     
       
    1. @FacialExpression //编译错误❌
    2. public class Action {
    3. // ... 为节省篇幅,省略其他代码
    4. }
  2. @Retention

    我们写的java代码会经过3个阶段:

    • 源代码阶段,也就是.java文件。
    • class文件阶段。
    • 虚拟机加载class文件到内存里,执行代码,这个阶段也叫运行时

    元注解@Retention中也只有一个value方法:

     
       
    1. RetentionPolicy value();

    RetentionPolicy也是一个枚举类型,它的三个枚举对象分别代表了注解在这3个阶段里是否被保留。

    • RetentionPolicy.SOURCE:在源文件中保留。
    • RetentionPolicy.CLASS:在class文件中保留
    • RetentionPolicy.RUNTIME:在运行时保留,也就是可以通过反射的方式获取到这种类型的注解。这种方式也是我们自定义注解的一般采用方式~

    比如我们在某个注解上头这么声明:

     
       
    1. @Retention(RetentionPolicy.SOURCE)

    那么这个注解只能在java源文件中看到,一旦经过编译,产生.class文件后,便找不到注解的踪影了。

    现在我们把我们的CellFormat的生命周期定义成RetentionPolicy.RUNTIME的,也就是运行时也能获取注解信息:

     
       
    1. import java.lang.annotation.ElementType;
    2. import java.lang.annotation.Retention;
    3. import java.lang.annotation.RetentionPolicy;
    4. import java.lang.annotation.Target;
    5. @Retention(RetentionPolicy.RUNTIME)
    6. @Target({ElementType.FIELD})
    7. public @interface CellFormat {
    8. String format();
    9. boolean isBold() default false;
    10. int fontSize() default 12;
    11. String align() default "left";
    12. }

    然后我们就可以在运行时通过反射方法获取到这些注解的信息咧:

     
       
    1. public class Test {
    2. public static void main(String[] args) {
    3. Class<Employee> clazz = Employee.class;
    4. try {
    5. Field field = clazz.getDeclaredField("name");
    6. Annotation[] annotations = field.getAnnotations();
    7. for (Annotation annotation : annotations) {
    8. System.out.println(annotation);
    9. }
    10. } catch (NoSuchFieldException e) {
    11. e.printStackTrace();
    12. }
    13. }
    14. }

    执行结果是:

     
       
    1. @CellFormat(align=center, isBold=true, fontSize=14, format=数值)

    更多关于获取注解信息的反射方法我们稍后再说哈~

  3. @Documented

    这个注解主要是和生成java文档有关的,如果在某个注解上加了这个元注解,则当该注解加在某段代码上时,使用javadoc工具会把注解也收录到文档中,默认的是不收录的。这个注解只有我们去生成注释文档时才有用,如果不生成的话,就不需要关心它。

  4. @Inherited

    如果在某个注解上加了这个元注解,则当该注解加在某个类上时,该类的子类也会继承到这个注解。详细使用情况我们稍后介绍~

注解解析器

上边我们使用java给我们提供的元注解成功的定义出了一个名叫CellFormat的注解,并且成功的给Action类的各个字段添加了这个注解。但是有啥用呢?

是的,啥用都没有,注解的添加不会影响原先代码的执行。因为它只是一种描述数据,所以谁关心,谁处理。要是没人关心放在那也没关系,等以后哪天有人想关了再分析代码呗~所以如果没有工具来读取注解,那么注解也不会比注释更有用

对于元注解来说,由于它们是java代码自己提供的,所以编译器或者java自带的一些工具会关心并解析这些元注解,比如某个注解上加了@Target,编译器会判断这个注解是否用到了合适的代码部分,如果不对会编译错误。而对于我们自定义的注解来说,注解并不会影响到编译器行为,对代码是没有任何影响的!

如果我们想处理一下我们的自定义注解,就需要了解一下关于注解的反射方法。

注解反射方法

设计java的大叔们在java.lang.reflect包中为我们定义了一个AnnotatedElement的接口:

 
 
  1. public interface AnnotatedElement {
  2. <T extends Annotation> T getAnnotation(Class<T> annotationClass);
  3. Annotation[] getAnnotations();
  4. Annotation[] getDeclaredAnnotations();
  5. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
  6. }

因为我们可以把注解放在代码的不同部分上,比如字段、方法、包名、类名啥的,所以代表不同代码部分的反射类,诸如ClassConstructorFieldMethodPackage都实现了AnnotatedElement接口。这些方法的具体作用是:

方法 用途
getAnnotation 返回该程序元素上存在的、指定类型的注解(包括通过继承的来的),如果该类型注解不存在,则返回null。
getAnnotations 返回该程序元素上存在的所有注解(包括通过继承的来的)。
getDeclaredAnnotations 判断该程序元素上是否包含指定类型的注解(不包括通过继承的来的),存在则返回true,否则返回false。
isAnnotationPresent 返回直接存在于此元素上的所有注解(不包括通过继承的来的)。

在测试这些方法之前把我们前边定义的两个注解再抄一遍:

 
 
  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Target({ElementType.FIELD})
  7. public @interface CellFormat {
  8. String format();
  9. boolean isBold() default false;
  10. int fontSize() default 12;
  11. String align() default "left";
  12. }
 
 
  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5. @Target({ElementType.METHOD})
  6. @Retention(RetentionPolicy.RUNTIME)
  7. public @interface FacialExpression {
  8. }

然后再定义一个通用的注解,并且它可以被继承:

 
 
  1. import java.lang.annotation.Inherited;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Inherited
  6. public @interface FullAnnotation {
  7. }

我们修改一下之前的Employee类,给类名上也加上注解:

 
 
  1. @FullAnnotation
  2. public class Employee {
  3. @CellFormat(format = "数值", isBold = true, fontSize = 14, align = "center")
  4. private String name; //姓名
  5. @CellFormat(format = "数值")
  6. private int age; //年龄
  7. @CellFormat(format = "文本")
  8. private String id; //身份证号
  9. @ValueAnnotation(value = "23", otherMethod = 1)
  10. @CellFormat(format = "货币", isBold = true)
  11. private float salary; //工资,单位:元
  12. // ... 为节省篇幅,省略字段的getter/setter方法和构造方法
  13. @FacialExpression
  14. public void laugh() {
  15. System.out.println("laughing");
  16. }
  17. @FacialExpression
  18. public void cry() {
  19. System.out.println("crying");
  20. }
  21. public void run() {
  22. System.out.println("running");
  23. }
  24. public void walk() {
  25. System.out.println("standing");
  26. }
  27. }

再定义一个继承自Employee的类:

 
 
  1. public class SubEmployee extends Employee {}

然后我们以ClassMethodField为例来测试一下AnnotatedElement中声明的这些方法:

 
 
  1. public static void main(String[] args) {
  2. Class<Employee> clazz = Employee.class;
  3. Annotation annotation = clazz.getAnnotation(FullAnnotation.class);
  4. System.out.println("通过getAnnotation方法获取Employee类上FullAnnotation类型的注解:" + annotation);
  5. Annotation[] annotations = clazz.getAnnotations();
  6. System.out.println("通过getAnnotations方法获取用于Employee类的注解数组:" + Arrays.toString(annotations));
  7. Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
  8. System.out.println("通过getDeclaredAnnotations方法获取用于Employee类的注解数组:" + Arrays.toString(declaredAnnotations));
  9. boolean isAnnotationPresent = clazz.isAnnotationPresent(FullAnnotation.class);
  10. System.out.println("Employee类上是否有FacialExpression的注解:" + isAnnotationPresent);
  11. System.out.println("---------");
  12. try {
  13. Field field = clazz.getDeclaredField("name");
  14. Annotation fieldAnnotation = field.getAnnotation(CellFormat.class);
  15. System.out.println("通过getAnnotation方法获取name字段上CellFormat类型的注解:" + fieldAnnotation);
  16. Annotation[] fieldAnnotations = field.getAnnotations();
  17. System.out.println("通过getAnnotations方法获取用于name字段的注解数组:" + Arrays.toString(fieldAnnotations));
  18. Annotation[] fieldDeclaredAnnotations = field.getDeclaredAnnotations();
  19. System.out.println("通过getDeclaredAnnotations方法获取用于name字段的注解数组:" + Arrays.toString(fieldDeclaredAnnotations));
  20. boolean isFieldAnnotationPresent = field.isAnnotationPresent(CellFormat.class);
  21. System.out.println("name字段上是否有CellFormat的注解:" + isFieldAnnotationPresent);
  22. System.out.println("---------");
  23. } catch (NoSuchFieldException e) {
  24. throw new RuntimeException(e);
  25. }
  26. try {
  27. Method method = clazz.getDeclaredMethod("laugh");
  28. Annotation methodAnnotation = method.getAnnotation(FacialExpression.class);
  29. System.out.println("通过getAnnotation方法获取laugh方法上CellFormat类型的注解:" + methodAnnotation);
  30. Annotation[] methodAnnotations = method.getAnnotations();
  31. System.out.println("通过getAnnotations方法获取用于laugh方法的注解数组:" + Arrays.toString(methodAnnotations));
  32. Annotation[] methodDeclaredAnnotations = method.getDeclaredAnnotations();
  33. System.out.println("通过getDeclaredAnnotations方法获取用于laugh方法的注解数组:" + Arrays.toString(methodDeclaredAnnotations));
  34. boolean isMethodAnnotationPresent = method.isAnnotationPresent(FacialExpression.class);
  35. System.out.println("laugh方法上是否有FacialExpression的注解:" + isMethodAnnotationPresent);
  36. System.out.println("---------");
  37. } catch (NoSuchMethodException e) {
  38. throw new RuntimeException(e);
  39. }
  40. Class<SubEmployee> clz = SubEmployee.class;
  41. Annotation subAnnotation = clz.getAnnotation(FullAnnotation.class);
  42. System.out.println("通过getAnnotation方法获取SubEmployee类上FullAnnotation类型的注解:" + subAnnotation);
  43. Annotation[] subAnnotations = clz.getAnnotations();
  44. System.out.println("通过getAnnotations方法获取用于SubEmployee类的注解数组:" + Arrays.toString(subAnnotations));
  45. Annotation[] subDeclaredAnnotations = clz.getDeclaredAnnotations();
  46. System.out.println("通过getDeclaredAnnotations方法获取用于SubEmployee类的注解数组:" + Arrays.toString(subDeclaredAnnotations));
  47. boolean isSubAnnotationPresent = clz.isAnnotationPresent(FullAnnotation.class);
  48. System.out.println("SubEmployee类上是否有FacialExpression的注解:" + isSubAnnotationPresent);
  49. }

执行结果是:

 
 
  1. 通过getAnnotation方法获取Employee类上FullAnnotation类型的注解:@FullAnnotation()
  2. 通过getAnnotations方法获取用于Employee类的注解数组:[@FullAnnotation()]
  3. 通过getDeclaredAnnotations方法获取用于Employee类的注解数组:[@FullAnnotation()]
  4. Employee类上是否有FacialExpression的注解:true
  5. ---------
  6. 通过getAnnotation方法获取name字段上CellFormat类型的注解:@CellFormat(align=center, fontSize=14, isBold=true, format=数值)
  7. 通过getAnnotations方法获取用于name字段的注解数组:[@CellFormat(align=center, fontSize=14, isBold=true, format=数值)]
  8. 通过getDeclaredAnnotations方法获取用于name字段的注解数组:[@CellFormat(align=center, fontSize=14, isBold=true, format=数值)]
  9. name字段上是否有CellFormat的注解:true
  10. ---------
  11. 通过getAnnotation方法获取laugh方法上CellFormat类型的注解:@FacialExpression()
  12. 通过getAnnotations方法获取用于laugh方法的注解数组:[@FacialExpression()]
  13. 通过getDeclaredAnnotations方法获取用于laugh方法的注解数组:[@FacialExpression()]
  14. laugh方法上是否有FacialExpression的注解:true
  15. ---------
  16. 通过getAnnotation方法获取SubEmployee类上FullAnnotation类型的注解:@FullAnnotation()
  17. 通过getAnnotations方法获取用于SubEmployee类的注解数组:[@FullAnnotation()]
  18. 通过getDeclaredAnnotations方法获取用于SubEmployee类的注解数组:[]
  19. SubEmployee类上是否有FacialExpression的注解:true

需要特别注意的是,由于FullAnnotation上被@Inherited注解了,所以被@FullAnnotation注解的类的子类也会继承这个注解,也就是说SubEmployee也会继承到@FullAnnotation这个注解,调用getAnnotationgetAnnotations可以获取到这个注解,但是getDeclaredAnnotationsisAnnotationPresent并不能获取到这个注解,大家需要注意到这个不同。

在知道了如何使用反射方法去获取注解信息后,我们想分析一下Employee类有哪些关于面部表情的方法,就可以这么写:

 
 
  1. public class Test {
  2. public static void main(String[] args) {
  3. Class<Employee> clazz = Employee.class;
  4. Method[] methods = clazz.getDeclaredMethods();
  5. List<String> annotationMethodNameList = new ArrayList<>();
  6. for (Method method : methods) {
  7. Annotation annotation = method.getAnnotation(FacialExpression.class);
  8. if (annotation != null) {
  9. annotationMethodNameList.add(method.getName());
  10. }
  11. }
  12. System.out.println("Action类标记了FacialExpression的方法有:" + annotationMethodNameList);
  13. }
  14. }

执行结果是:

 
 
  1. Action类标记了FacialExpression的方法有:[laugh, cry]

如果你还想按照自己的需求去解析一下,那就自己写点代码去解析吧~

再一次提醒,编译器是不关心我们的自定义注解的,如果想处理,需要另外再写代码分析,这也是为啥我们的自定义注解的生命周期一般都是运行时。那能不能让编译器去处理我们的自定义注解呢?

答:嗯,可以,你需要去修改一下编译过程,也就是需要定制一下编译器,这玩意儿可以做,但是我不会~

内置注解

java中自带了3种定义好的注解,也叫内置注解。这三种注解都会被编译器关注,也就是说这些注解会对编译造成影响。

  1. Override

    看一下这个注解的定义:

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

    说明Override只能标记在方法上头,并且只对源代码有效,也就是说编译成.class文件就看不见这个注解了,它的使用方法我们已经见了很多遍了:

     
       
    1. public class Father {
    2. public void play() {
    3. System.out.println("打麻将");
    4. }
    5. }
     
       
    1. public class Child extends Father {
    2. @Override //表示这个方法是覆盖父类中的
    3. public void play() {
    4. System.out.println("玩尿泥");
    5. }
    6. }

    编译器在分析源代码的时候,如果某个方法上编标记了这个注解,编译器会去查找它的父类中是否有同样的方法,如果没有的话,编译器就会报错来提醒我们程序员写错啦!

  2. @SuppressWarnnings

    看一下它的定义:

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

    这个注解可以用在类、字段、方法、方法参数、构造方法以及局部变量上,也是只对源代码有效。

    如果某段代码上可能有些不安全的操作,比方说我们前边遇到的将某个类型转型为泛型类型,编译器会发出警告。但是如果在发出警告的代码上加了这个注解,编译器就不会再发出警告提示了。

    大家看到这个注解里有一个value方法,它的返回类型是一个数组,数组元素的取值可以是下边这些,代表着屏蔽不同类型的警告:

    • deprecation:使用了不赞成使用的类或方法时的警告;
    • unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
    • fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
    • path:在类路径、源文件路径等中有不存在的路径时的警告;
    • serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
    • finally:任何 finally 子句不能正常完成时的警告;
    • all:关于以上所有情况的警告。

    上边我们最熟悉的要数unchecked警告了哈。因为value方法的返回类型是一个数组,所以在使用的时候需要把各个元素用大括号{}扩起来,比如我们要屏蔽pathunchecked警告,就可以写成这样:

     
       
    1. @SuppressWarnnings({"path", "unchecked"})

    如果只屏蔽1个警告,那可以省略大括号:

     
       
    1. @SuppressWarnnings("unchecked")
  3. @Deprecated

    看一下它的定义:

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

    这种注解可以作用在代码的所有部分处,然后它在运行时还可以被反射方法获取到。

    这个注解表明某段代码不鼓励被使用,比如采用了比较慢的实现算法,或者可能引发某种危险啥的,总之最好别用。比如我们加在Employeewalk方法上:

     
       
    1. public class Employee {
    2. // ... 为节省篇幅,省略其他方法和字段
    3. @Deprecated
    4. public void walk() {
    5. System.out.println("standing");
    6. }

    然后在别处调用这个方法的时候编译器就会有警告

     
       
    1. public class Test {
    2. public static void main(String[] args) {
    3. new Employee().walk();
    4. }
    5. }

    执行javac Test.java的时候会发出警告:

     
       
    1. 注: Test.java使用或覆盖了已过时的 API
    2. 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。

再次强调一遍,这3个注解是java内置的注解,编译器会关心并处理它们。但是对于我们的自定义注解,编译器可不会关心,如果我们需要处理,需要自己写程序去处理的。

总结

image_1bvto1p3e1f0crd8gfc1uci10ab9.png-373.2kB

  1. 元数据就是对数据的描述数据。
  2. 注解是java代码各部分的元数据
  3. 注解本质是一种继承自java.lang.Annotation的接口,它的每一个方法称为一个注解元素。
  4. 注意事项:

    • 每个注解元素必须是public的,如果不填访问权限则默认是public的。
    • 方法返回类型必须是下边的这些类型:

      • 所有基本数据类型(intfloatbooleanbytedoublecharlongshort)
      • String类型
      • Class类型
      • enum类型
      • Annotation类型,它是所有注解的祖宗接口
      • 以上所有类型的数组
    • 如果注解的方法名是value,并且它是唯一的一个需要赋值的注解元素的时候,则可以在使用时在小括号()中直接填写值。

  5. java提出了4种元注解来描述自定义注解:

    • @Target
    • @Retention
    • @Documented
    • @Inherited
  6. java自带了3种注解,分别是:

    • @Override
    • @SuppressWarnnings
    • @Deprecated
  7. java在AnnotatedElement接口中提供了一些获取注解信息的反射方法:

    方法 用途
    getAnnotation 返回该程序元素上存在的、指定类型的注解(包括通过继承的来的),如果该类型注解不存在,则返回null。
    getAnnotations 返回该程序元素上存在的所有注解(包括通过继承的来的)。
    getDeclaredAnnotations 判断该程序元素上是否包含指定类型的注解(不包括通过继承的来的),存在则返回true,否则返回false。
    isAnnotationPresent 返回直接存在于此元素上的所有注解(不包括通过继承的来的)。
  8. 特别注意,java自带的3中注解和4种元注解都会被编译器关注并解析,而我们的自定义注解只是对代码的一种描述,本身一毛钱卵用也没有。如果你想使用,就编写自己的程序去解析,所以我们一般把注解的生命周期定义成运行时的,去调用反射方法去分析注解。

猜你喜欢

转载自blog.csdn.net/qq_38836118/article/details/79760849
今日推荐