注解之所以强大,能被众多框架所使用的主要原因在于,它可以允许程序员自定义注解,使Java程序变成自描述的。注解的语法形式和接口差不多,只是在interface前面多了一个“@”符号。

      上面的代码是一个最简单的注解,这个注解没有属性。我们可以在自定义注解时定义属性,在注解类型的定义中以无参方法的形式来声明,其方法名和返回值分别定义了该属性的名字和类型,其代码如下:

    1. //定义一个属性value
    2. String value();
    3. }

      可以按如下格式使用MyAnnotation注解:

    1. public class TestAnnotation{
    2. //如果没有写属性名,而这个注解又有value属性,则将这个值赋给value属性
    3. //@MyAnnotation("good")
    4. @MyAnnotation(value = "good")
    5. public void getObjectInfo(){
    6. }
    7. }

      接下来修改自定义注解MyAnnotation,使其含两个属性,具体代码如下:

    1. public @interface MyAnnotation{
    2. //定义两个属性name和age
    3. String name();
    4. int age();
    5. }

      在注解中可以定义属性,也可以给属性赋默认值,具体代码如下:

      定义了注解之后,接下来就可以在程序中使用注解,具体代码如下:

    1. public class TestAnnotation{
    2. //使用带属性的注解时,需要为属性赋值
    3. @MyAnnotation(name = "柳海龙",age = 24)
    4. //@MyAnnotation
    5. public void getObjectInfo(){
    6. }
    7. }

      请注意注释的描述,使用带属性的注解时,需要给属性赋值。不过如果在定义注解时给属性赋了默认值,则可使用不带属性值的注解,也就是让注解使用自己的默认值。

    8.4.2 元注解

      上一小节虽然学习了自定义注解,但是肯定有不少人觉得学完之后,心里空空的,不知道自定义注解到底有什么用?下面的内容就是来解决这个问题的。

      在自定义注解时,注解看起来和类、接口比较类似,尤其看起来更像接口。注解可以理解为和接口一样,是程序的一个基本组成部分。既然可以对类、接口、方法和属性等进行注解,那么当然也可以对注解进行注解。

      使用不同注解对注解进行注解的方法,和对类、接口进行注解的方法一样,所不同的是,Java为注解单独提供了四种元注解,即@Target、@Retention、@Documented和@Inherited,下面分别介绍这四种元注解。

      @Target元注解很容易理解,Target中文含义为目标,使用@Target注解的目的是用于指定被修饰的注解能用于修饰哪些程序元素。如果注解定义中不存在@Target元注解,则此注解可以用在任一程序元素上,如果存在这样的元注解,则编译器强制实施指定的使用限制。

      接下来看这样一个案例,将之前自定义的注解用@Target进行注解,以限制此注解只能使用在属性上。此时如果将此注解使用在方法上,编译器会报出“注释类型不适用于该类型的声明”的错误。案例具体代码如下:

    1. import java.lang.annotation.*;
    2. //限制此注解只能使用在属性上
    3. @Target({ElementType.FIELD})
    4. public @interface MyAnnotation{
    5. String name() default "姓名";
    6. int age() default 22;
    7. }
    8. public class TestAnnotation{
    9. @MyAnnotation
    10. public void getObjectInfo(){
    11. }
    12. }

      @Target注解的属性value可以为以下值,被@Target注解的注解只能用来注解对应的目标。

    • ElementType.ANNOTATION_TYPE:注解类型声明。

    • ElementType.CONSTRUCTOR:构造方法声明。

    • ElementType.FIELD:字段声明(包括枚举常量)。

    • ElementType.LOCAL_VARIABLE:局部变量声明。

    • ElementType.METHOD:方法声明。

    • ElementType.PACKAGE:包声明。

    • ElementType.PARAMETER:参数声明。

    • ElementType.TYPE:类、接口(包括注解类型)或枚举声明。

    8.4.4 @Retention注解

      @Retention 元注解用于指定被修饰的注解可以保留多长时间。如果注解定义中不存在 @Retention元注解,则保留策略默认为RetentionPolicy.CLASS。

    • RetentionPolicy.CLASS:编译器将把注解记录在class文件中,当运行Java程序时,虚拟机不再保留注解。

    • RetentionPolicy.RUNTIME:编译器将把注解记录在class文件中,当运行Java程序时,虚拟机保留注解,程序可以通过反射获取该注解。

    • RetentionPolicy.SOURCE:编译器将直接丢弃被修饰的注解。

      接下来通过一个案例来演示通过反射获取注解,具体代码如下。案例中提供了较为详细的注释,请大家认真阅读。

    1. import java.lang.annotation.*;
    2. @Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD
    3. ,ElementType.PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
    4. //当运行Java程序时,虚拟机保留注解
    5. @Retention(RetentionPolicy.RUNTIME)
    6. public @interface MyAnnotation{
    7. String name() default "姓名";
    8. int age() default 22;
    9. }
    10. import java.lang.annotation.Annotation;
    11. public class TestAnnotation{
    12. public static void main(String[] args) throws SecurityException,
    13. NoSuchMethodException, ClassNotFoundException {
    14. TestAnnotation ta = new TestAnnotation();
    15. ta.getObjectInfo();
    16. }
    17. @MyAnnotation
    18. @Deprecated
    19. public void getObjectInfo() throws ClassNotFoundException,
    20. SecurityException, NoSuchMethodException{
    21. //利用反射机制获取注解
    22. Annotation[] arr = Class.forName("TestAnnotation")
    23. .getMethod("getObjectInfo").getAnnotations();
    24. //遍历每个注解对象
    25. for(Annotation an:arr){
    26. if(an instanceof MyAnnotation){//如果注解是MyAnnotation类型
    27. System.out.println("MyAnnotation注解:" + an);
    28. System.out.println("MyAnnotation注解的name属性值:"+((MyAnnotation)an).
    29. name());
    30. System.out.println("MyAnnotation注解的age属性值:"+((MyAnnotation)an).age());
    31. }else{
    32. System.out.println("非MyAnnotation注解:" + an);
    33. }
    34. }
    35. }
    36. }

      代码中getObjectInfo()方法有两个注解,@MyAnnotation和@Deprecated,其中,自定义注解@MyAnnotation的元注解@Retention的值为RetentionPolicy.RUNTIME,含义为当运行Java程序时,虚拟机保留注解,所以在运行时可以通过反射机制获取该注解。程序运行结果如图8.3所示。@Deprecated为内建注解,通过运行结果可以看出@Deprecated的元注解@Retention的值也是RetentionPolicy.RUNTIME。


    图8.3 通过反射机制获取注解

      一看这个元注解的名字,就知道它一定和文档有关。在默认的情况下使用javadoc工具自动生成文档时,注解将被忽略掉。如果想在文档中也包含注解,必须使用@Documented 为文档注解。@Documented注解类型中没有成员变量。如果定义注解时使用了@Documented修饰,则所有使用该注解修饰的程序元素的API文档中都将包含该注解说明。

      使用javadoc生成文档,产生的文档对TestAnnotation类的描述如下:

    1. class TestAnnotation extends java.lang.Object

      如果取消对@Documented的注释,使其起作用,将会出现另一个结果:

    1. @MyAnnotation
    2. class TestAnnotation extends java.lang.Object

    8.4.6 @Inherited注解

      继承是面向对象的特性之一,Java是典型的面向对象语言,所以继承是Java语言典型的特性。前面讲过,注解是程序的一个基本组成部分,那么父类的注解是否被子类继承呢?默认情况下,父类的注解不被子类继承,如果要想继承父类注解,就必须使用@Inherited元注解。接下来通过下面的三小段代码,介绍@Inherited注解的含义。

    1. import java.lang.annotation.*;
    2. @Inherited
    3. public @interface MyAnnotation{
    4. String name() default "姓名";
    5. int age() default 22;
    6. }
    7. @MyAnnotation
    8. public class Vehicle
    9. {
    10. public void drive()
    11. {
    12. //省略若干代码
    13. }
    14. }
    15. public class Truck extends Vehicle
    16. {
    17. //省略若干代码