这一次我们来看看注解的Annotation接口。
首先我们要知道什么是注解,注解这个特性是java5引入进来的,主要是在代码上附带上元数据或标记。这样的做法使代码与一些框架的配置项很好的结合起来,且增加了代码的可读性,Hibernate注解就是非常成功的使用。
Java本身为我们提供了三个注解,他们分别是
java,lang.Override
@Override注解说明该方法覆盖了父类的某个方法,在编译时编译器将对该方法进行检查,如果出现拼写错误,或父类并没有这个方法将报错。
java,lang.Deprecated
@Deprecated注解说明该类、方法、成员变量已经不再建议使用了,一般IDE会将@Deprecated的类、方法、成员变量划掉以提示开发者。
java,lang.SuppressWarnings
@SuppressWarnings可以消除编译器的warning,在你非常确定一个类、方法、变量的warning是可以忽略的时候,你可以使用这个注解。
那么我们以下面这个超简单的例子来说明注解是如何使用的:
@Deprecated
public class Test {
}
我们可以看到我们为Test类添加了@Deprecated注解,于是Test被IDE划掉了。在Java中注解可以反射成Annotation的对象,而我们获取Annotation的办法则是通过一个AnnotatedElement接口来实现。Class正好实现了这个接口。
在AnnotatedElement中一共有
<T extends Annotation> T getAnnotation(Class<T> annotationType);
boolean isAnnotationPresent(Class<? extends Annotation> annotationType);
Annotation[] getAnnotations();
Annotation[] getDeclaredAnnotations();
这四个方法。
首先我们就以刚刚那个例子为例,来试一下如何获取类的@Deprecated注解对象
@Deprecated
public class Test {
public static void main(String[] args) {
Annotation aOverride = new Test().getClass().getAnnotation(Override.class);
System.out.println("new Test().getClass().getAnnotation(Override.class)=" + aOverride);
//输出new Test().getClass().getAnnotation(Override.class)=null
Annotation aDeprecated = new Test().getClass().getAnnotation(Deprecated.class);
System.out.println("new Test().getClass().getAnnotation(Deprecated.class)=" + aDeprecated);
//输出new Test().getClass().getAnnotation(Deprecated.class)=@java.lang.Deprecated()
}
}
我们可以看到的确把这个Annotation给获取出来了,但我们发现我们必须预先知道注解的类型才能获取出来,那么有没有办法来判断将要获取的注解是什么类型呢?一个简单的思路就是通过调用
boolean isAnnotationPresent(Class<? extends Annotation> annotationType);
方法以及if-else来判断我们的类使用了哪一个注解,从而调用对应的getAnnotation方法
@Deprecated
public class Test {
public static void main(String[] args) {
Class<? extends Test> t = new Test().getClass();
Annotation a;
if (t.isAnnotationPresent(Override.class)) {
a = t.getAnnotation(Override.class);
} else if (t.isAnnotationPresent(Deprecated.class)) {
a = t.getAnnotation(Deprecated.class);
} else if (t.isAnnotationPresent(SuppressWarnings.class)) {
a = t.getAnnotation(SuppressWarnings.class);
} else {
a = null;
}
System.out.println("a=" + a);//输出a=@java.lang.Deprecated()
}
}
在控制台我们可以看到,的确获取了正确的注解对象了。但有时我们需要自行添加一些新的注解,如果按照这样的思路,不断添加if-else语句显然是不科学的。那怎么才能在不知道注解类型的情况下获注解对象呢?AnnotatedElement接口中的getAnnotations方法就解决了这个问题,我们还是以上面的那个例子为例,获取一下@Deprecated注解的对象
@Deprecated
public class Test {
public static void main(String[] args) {
Class<? extends Test> t = new Test().getClass();
Annotation[] arg = t.getAnnotations();
System.out.println("arg.length=" + arg.length);//arg.length=1
Annotation a = arg[0];
System.out.println("a=" + a);//a=@java.lang.Deprecated()
}
}
在控制台我们看到我们在不知道注解类型的情况下获取了注解对象。
getAnnotations()方法可以帮我们在不知道注解类型的情况下获取在类、方法、变量上面的全部注解。在这我们自定义一个注解为@MyAnnotation
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String hello();
}
然后注解到我们的Test类上,然后调用getAnnotations方法
@Deprecated
@MyAnnotation(hello="你好")
public class Test {
public static void main(String[] args) {
Class<? extends Test> t = new Test().getClass();
Annotation[] arg = t.getAnnotations();
System.out.println("arg.length=" + arg.length);//arg.length=2
for (Annotation a : arg) {
System.out.println(" a=" + a);
}
}
}
我们看到两个注解的对象都被我们获取回来了。
仔细观察的朋友可能会发现在AnnotatedElement类中还有一个getDeclaredAnnotations方法,它的返回值与getAnnotations方法是一样的。那他们有些什么区别的呢?
首先我们要知道注解是可以继承的,就是说子类会继承父类的一些注解(该注解在定义时一定要声明为@Inherited,而且方法和变量上的注解不一定能继承)。我们再编写一个Test2类来给Test继承
@MyAnnotation(hello="你好")
class Test2 {
}
@Deprecated
public class Test extends Test2{
public static void main(String[] args) {
Class<? extends Test> t = new Test().getClass();
Annotation[] ta = t.getAnnotations();
System.out.println("ta.length=" + ta.length);
for (Annotation temp: ta){
System.out.println(temp);
}
Annotation[] tda = t.getDeclaredAnnotations();
System.out.println("tda.length=" + tda.length);
for (Annotation temp: tda){
System.out.println(temp);
}
}
}
我们可以看到在控制台里面tda只输出了一条信息,而ta则有两条。就是因为tda屏蔽了由Test2继承而来的@Deprecated。
在我们大体知道如何利用反射机制获取注解对象之后,我们来看看Annotation接口都为我们提供了哪些方法。
首先我们来看看equals和hash方法
@MyAnnotation(hello="你好")
public class Test{
public static void main(String[] args) {
Annotation a1 = new Test().getClass().getAnnotation(MyAnnotation.class);
Annotation a2 = new Test2().getClass().getAnnotation(MyAnnotation.class);
boolean temp = a1.equals(a2);
System.out.println(temp + "=Test的MyAnnotation==Test2的MyAnnotation");
System.out.println("a1=" + a1.hashCode());
System.out.println("a2=" + a2.hashCode());
}
}
@MyAnnotation(hello="你好")
class Test2 {
}
从控制台我们发现Annotation对象有这样的规则,只要是相同的注解,不管它注解的是哪个类都将指向同一个空间,这一点有些类似字符串常量的机制。
而Annotation的另一个方法toSting()则只返回Annotation对象的类以及其成员变量,它并没有像Object的成员变量那样既显示出hashcode又显示出对象的类型。
最后在Annotation类里,还有一个比较实用的方法
Class<? extends Annotation> annotationType();
这个方法可以返回当前注解对象的注解类型,这样我们就可以在不知道注解类型的情况下反射获取注解对象并获取出它的类型来进行一些特别的处理了。
@MyAnnotation(hello="你好")
public class Test{
public static void main(String[] args) {
Annotation a = new Test().getClass().getAnnotations()[0];
Class<? extends Annotation> c = a.annotationType();
System.out.println(c);
}
}
从上面的这个例子我们可以看到,我们在不知道不了解注解类型的情况情况下取得了注解的对象以及注解类型。
通过这个方法我们就可以进行许多方便有效的注解反射操作了。