从JDK1.5开始,Java就增加了Annotation这个新的功能,这种特性被称为元数据特性,同时也被称为注释。
系统内建的Annotation:
提醒:以下这三个系统内建的Annotation位于java.lang包下
1.@Override,相信大家对这个比较熟悉,如果我们要重写一个类的方法的时候,要加上这个注解,但是很多人会反问,不加也是没问题的,但是我们必须考虑到的是程序的正确性,如果你本身的意图是重写这个方法,但是你在写的时候把方法名写错了,那么这就不是重写了,也改变了意图,所以在重写的方法上加上这个注解是防患于未然,也是明确的告诉别人我这个方法是重写的,如何我写错方法名,那么这个注解也会提醒我。
2.@Deprecated,表示这个程序元素很危险或者存在更好的选择,不鼓励采用这种元素。
1 @Deprecated 2 public class DeprecatedDemo { 3 4 @Deprecated 5 public static final String name = "xujianguo"; 6 7 @Deprecated 8 public void print() { 9 System.out.println("This is the deprecated method"); 10 } 11 }
上面这个类说明了@Deprecated注解也是用在类上,成员属性上,还有方法上,这个类用起来不会报错,但会有警告的信息,表示你所用的东西已经过时了。
3.@SuppressWarnings,表示取消显示指定的编译器警告,通过这个注解我们可以取消一个不必要的警告,如泛型警告和过时警告,通过查看API文档我们可以发现,该Annotation下有一个value的属性,返回值一个String类型的数组,具体为:public abstract String[] value,这个属性其实是一个警告集,里面用放的是@SuppressWarnings可以压制的警告。
警告集:
关键字 | 关键字 |
deprecation | 使用了不赞成使用的类或者方法的警告 |
unchecked | 执行了未检查的转换警告,如泛型操作中没有指定泛型 |
fallthrough | 当switch程序块执行到下种情况时没有break语句的警告 |
path | 在类路径、源文件路径等中有着不存在路径时的警告 |
serial | 当在可序列化类上缺少serialVersionUID定义时的警告 |
finally | 任何finally子句不能完成时的警告 |
all | 关于以上所有的警告 |
下面演示一个压制deprecatation和unchecked警告的Demo:
1 /** 2 * 该类实现了序列化接口,若无serialVersionUID会出现警告 3 * @author Guo 4 */ 5 @SuppressWarnings({"serial", "unchecked"}) 6 public class SuppressWarningsDemo implements Serializable{ 7 8 public static void main(String[] args) { 9 10 /** 11 * 没有指定泛型,出现警告 12 * @author Guo 13 */ 14 List<String> list = new ArrayList(); 15 } 16 }
自定义Annotation:
定义自己的Annotation非常简单,就像定义一个接口那样:
1 [public] @interface MyAnnotation { 2 3 }
格式很简单,一个Annotation可能接收各种参数,就像SuppressWarnings注解那样,里面可以接收一个数组,下面我们介绍一个它参数定义:
1.基本变量,可以是String类型的,也可以是int类型的,格式:public 类型 变量名();
2.数组类型,数组的定义格式也是大同小异:public 类型[] 变量名();
3.枚举类型,通过定义枚举类型,就可以限定注解里面的内容,格式:格式:public enum 变量名();
4.默认值,在Annotation中写好默认值,在别的类上使用注解的时候就可以不用写值了,格式:public 类型 变量名() default 默认值;
下面简单演示一下,大家加深一下印象:
1 enum MyEnum { 2 xp, win7, linux; 3 } 4 5 public @interface MyAnnotation { 6 7 public String name() default "xujianguo"; 8 public int age() default 20; 9 public String[] array(); 10 public MyEnum system() default MyEnum.linux; 11 } 12 13 class Test { 14 15 @MyAnnotation(name="zyp", age=20, array = {"zhou", "yan", "ping"}, system=MyEnum.win7) 16 public static void main(String[] args) { 17 System.out.println("Just Test"); 18 } 19 }
现在我们要讨论一个问题,你自定义好的就能在JVM跑吗,其实你看看API中系统内建的三个Annotation,它们都使用了一个注解@Retention,这个注解位于java.lang.annotation包下,其实这个包下还有几个Annotation,我们也介绍一个,不过重点的是@Retention:
1.@Documented,指示某一类型的注释将通过 javadoc 和类似的默认工具进行文档化。应使用此类型来注释这些类型的声明:其注释会影响由其客户端注释的元素的使用。如果类型声明是用Documented 来注释的,则其注释将成为注释元素的公共 API 的一部分。简单的说就是用了这个注解,你以后用该Annotation的时候在上面加上注释会被记录到文档上。
2.@Inherited,指示注释类型被自动继承。如果在注释类型声明中存在 Inherited元注释,并且用户在某一类声明中查询该注释类型,同时该类声明中没有此类型的注释,则将在该类的超类中自动查询该注释类型。此过程会重复进行,直到找到此类型的注释或到达了该类层次结构的顶层(Object) 为止。如果没有超类具有该类型的注释,则查询将指示当前类没有这样的注释。简单的说就是这个注解相当于extends啊,父类如果有了某个注解,那个这个注解在子类中也是拥有的,通过反射也可以拿到注解上的信息。
3.@Target,指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上。如果存在这样的元注释,则编译器强制实施指定的使用限制。简单的说这个注解就是限制我们的Annotation可以用在什么地方,它有个value属性,是ElementType类型的,ElementType中规定以下几种范围:
范围 |
描述 |
ANNOTATION_TYPE |
只能用在注释声明上 |
CONSTRUCTOR |
只能用在构造方法上 |
FIELD |
只能用在字段的声明上 |
LOCAL_VARIABLE |
只能用在局部变量的声明上 |
METHOD |
只能用在方法的声明上 |
PACKAGE |
只能用在包的声明上 |
PARAMETER |
只能用在参数的声明上 |
TYPE |
只能用在类、接口、枚举类型上 |
提醒:其实这些限制的范围是可以叠加的,例如,你的Annotation想在类或者方法上使用,可以这么写:@Target(ElementType.TYPE, ElementType.METHOD)
4.@Retention,指示注释类型的注释要保留多久。这个注释有个value的属性,属性的类型为RetentionPolicy,而RetentionPolicy里面有三个常变量,我们一起来看看这三个常变量。
范围 | 描述 |
SOURCE | 此Annotation的信息只会保存在程序源文件中(java文件),不会保留在编译好的文件中(class文件) |
CLASS |
此Annotation的信息保留在程序源文件(java文件)和编译好的文件中(class文件),使用此类的时候 Annotation的信息不会被加载到JVM中,如果一个Annotation没有声明使用什么范围,这个就是默认范围。 |
RUNTIME | 此Annotation的信息会保留在源文件、类文件中,还会被加载到JVM中 |
很明确的看出RUNTIME才是我们想要的菜,因为我们要利用Annotation去获取一些信息,我们也来看看我们系统内建的Annotation会属于哪些呢?@Override采用的是Retention(value=RetentionPolicy.SOURCE),@Deprecated采用的是Retention(value=RetentionPolicy.RUNTIME),@SuppressWarnings采用的也是Retention(value=RetentionPolicy.SOURCE),总结一下,一个能真正对我们来说起作用的Annotation应该这样定义:
1 @Retention(value=RetentionPolicy.RUNTIME) 2 public @interface MyAnnotation { 3 4 public String name() default "xujianguo"; 5 public int age() default 20; 6 public String[] array(); 7 public MyEnum system() default MyEnum.linux; 8 }
Annotation与反射:
说到Annotation的应用,则不会离开反射,在Class类中存在以下几种跟Annotation操作相关的方法:
方法 | 描述 |
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) | 如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null |
public Annotation[] getAnnotations() |
返回此元素上存在的所有注释 |
public Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。 |
public boolean isAnnotation() | 判断元素是否表示一个注释 |
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注释存在于此元素上,则返回 true,否则返回 false |
下面我自定义一个Annotation,用这个Annotation来模仿JUnit的@Test注解,同时自定义的这个Annotation也有属性,要将这个属性的值拿出来:
自定义的Annotation-TestSimulation:
1 @Retention(value=RetentionPolicy.RUNTIME) 2 public @interface TestSimulation { 3 public String author() default "xujianguo"; 4 }
运用注解的类AnnotationDemo类:
1 public class AnnotationDemo { 2 3 @TestSimulation(author="zhouyanping") 4 public void print() { 5 System.out.println("This is the method of print"); 6 } 7 8 public void say() { 9 System.out.println("This is the method of say"); 10 } 11 12 @TestSimulation 13 public void coding() { 14 System.out.println("This is the method of coding"); 15 } 16 }
进行反射解析的AnnotationUtil类:
1 public class AnnotationUtil { 2 3 public static void main(String[] args) throws Exception { 4 5 /** 6 * 反射类的对象和拿出一个方法组 7 * @author Guo 8 */ 9 Class clazz = Class.forName("com.xujianguo.test.AnnotationDemo"); 10 Object object = clazz.newInstance(); 11 Method[] methods = clazz.getMethods(); 12 13 for(Method method : methods) { 14 15 /** 16 * 对方法上的注解进行核对 17 * @author Guo 18 */ 19 if(method.isAnnotationPresent(TestSimulation.class)) { 20 21 /** 22 * 拿到指定的Annotation并获取相应的信息 23 * @author Guo 24 */ 25 TestSimulation ts = method.getAnnotation(TestSimulation.class); 26 System.out.println(ts.author()); 27 method.invoke(object); 28 } 29 } 30 } 31 }