Java注解与反射的使用

打开 Eclipse,新建 Java 项目“注解与反射”,在 src 下右键并建立包 “注解与反射”,在包下右键并建立 Annotation (注解)文件,名称为“ItAnnotation”。

注解与反射调用

ItAnnotation.java 文件

package 注解与反射;

public @interface ItAnnotation {

}

以上就是一个注解文件。然后再新建一个 class 文件,取名为“TestAnnotation”

TestAnnotation.java 文件

public class TestAnnotation {
    public static void main(String[] args) {
    
    }
}

然后在类的上方加上 “@ItAnnotation”,并在 main 方法中检查注解是否存在。我们知道java代码编译后其实是一个以 class 文件存放。检查的方法就是

TestAnnotation.class.isAnnotationPresent(ItAnnotation.class)

如图所示:
图1.png

如果 TestAnnotation 方法上方存在该注解则进行下一步操作,获取该方法指定类型的注解,代码如下:

ItAnnotation annotation = (ItAnnotation)TestAnnotation.class.getAnnotation(ItAnnotation.class);

然后打印输出 annotation 查看究竟,如图所示:
图2.png
实际上什么都没有打印,这是为什么呢?我们切换到 ItAnnotation.java 文件,给注解添加一个元注解。我们知道“元”这个字往往是这个意思,注解的注解就是元注解,专门给注解服务的注解。
在注解的上方添加一个 @Retention(RetentionPolicy.RUNTIME) 表示修饰下方的注解始终不会丢弃,运行期间也保留该注解。如图所示:
图3.png
现在再次运行,就会打印出:@注解与反射.ItAnnotation()

这边引出了个概念,@Retention(RetentionPolicy.RUNTIME)的意思是运行时不丢弃,也就是说注解是会被丢弃的,那么注解到底在什么时候会被丢弃呢?其实还有另外两个关于 @Retention 元注解的生命周期分别如下:

RetentionPolicy.SOURCE 编译阶段就丢弃,不会生成在 class 文件里
RetentionPolicy.CLASS 在生成 class 文件的时候,会保留在 class 文件里

现在对 @Retention 元注解有一点概念了,那么还有其他的元注解么?有,接下来接触的就是 @Target,它的作用是他修饰的注解可以用在什么地方,也就是可以在哪个代码块区域的上方使用。如果不加 @Target 的话就是任何地方都可以用。

ElementType.TYPE:    用于类、接口、枚举(TYPE实际上是 class 的父级)
ElementType.FIELD:
ElementType.METHOD     用于方法
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE
ElementType.PACKAGE

切换回 ItAnnotation.java 文件,试着加上 @Target(ElementType.METHOD)第二个元注解,如图所示:
图4.png
然后切换回“TestAnnotation.java” 文件你会发现 IDE 报错了,把 “@ItAnnotation” 移动到 main 方法上方,错误就消失了。那是因为 ElementType.METHOD 只能用于方法。

我们将 @Target 进行修改,让它能够多选,一般来讲多选都是在括号里面加个大括号,改为 @Target({ElementType.METHOD,ElementType.TYPE}) ,这样就可以同时用于类和方法上。

为注解增加一些属性

其实这个注解很像接口,我们在里面定义的相当于抽象方法。定义一个 String name(),记住 Ctrl+S 保存代码。代码如下:
ItAnnotation.java 文件

......
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface ItAnnotation {
    String name();    //增加一个字符串的属性
}

切换回 “TestAnnotation.java” 可以发现两个 @ItAnnotation 均报错,我们先删除main方法上方的注解,然后在注解后方加上括号填入属性值: @ItAnnotation(name = "小明"),最后在这个文件 main 方法中之前通过 ItAnnotation annotation 获取的对象调用该方法:annotation.name(),如下图所示:

TestAnnotation.java 文件
图5.png
再次运行,控制台会输出“小明”。

有1种特殊的情况,可以在注解中省去属性名称并直接赋值。我们切换回 “ItAnnotation.java” 文件将 String name() 改为 String value(),点击保存,再在“TestAnnotation.java”文件中,将注解中的值保留,前面的属性去除:@ItAnnotation("小明"),然后将annotation.name() 改为 annotation.value() 再次运行,发现依然可以输出“小明”,这个情况是特殊的,当你定义的抽象方法为 value() 时,且只有1个的情况下,注解中可以省略属性名并直接赋值。

在拥有多个抽象方法(同时包含 value())时,直接赋值会报错,但在其他方法拥有默认值的情况下不会。切换回 “ItAnnotation.java” 文件,将抽象方法改为 String name default "小明"; ,下方在加个 String value();,完整代码如下:

ItAnnotation.java 文件

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface ItAnnotation {
    String name() default "小明";
    String value();
}

TestAnnotation.java 文件

@ItAnnotation("ABC")
public class TestAnnotation {
    
    public static void main(String[] args) {
        //检查指定的类上方的注解是否存在
        if (TestAnnotation.class.isAnnotationPresent(ItAnnotation.class)) {
            //获取 ItAnnotation 类型的注解
            ItAnnotation annotation = (ItAnnotation)TestAnnotation.class.getAnnotation(ItAnnotation.class);
            System.out.println(annotation.name());
            System.out.println(annotation.value());
        }
    }
}

以上代码不会报错,再次运行,输出 name 的默认值(小明)与value的值(ABC)。