注解 (Annotation) 就是一种标签,可以插入到源代码中,我们的编译器可以对他们进行逻辑判断,或者我们可以自己写一个工具方法来读取我们源代码中的注解信息,从而实现某种操作。需要申明一点,注解不会改变编译器的编译方式,也不会改变虚拟机指令执行的顺序,它更可以理解为是一种特殊的注释,本身不会起到任何作用,需要工具方法或者编译器本身读取注解的内容继而控制进行某种操作。本篇文章将从以下几点详细的介绍下 Java 注解的使用:
一、元数据和注解 元数据(meta-data)就是指用来描述数据的数据,它往往是以标签的形式出现,主要用于描述代码块之间的联系。我们的注解就是一种元数据,根据它所起到的作用,我们可以大致将它分为以下三类:
二、标准注解(系统自带) 在我们 jdk 的 java.lang 包中定义了三个注解,他们是:@Override,@Deprecated,@SuppressWarnnings。Override 这个注解我们经常会使用到,在子类重写父类方法的时候就会使用到,他会帮助我们校验格式,确保我们正在定义的方法是在重写了父类的对应方法。Deprecated 注解一般修饰在类或者方法之前,用于表示该方法或者类已经不再推荐使用了。SuppressWarnnings 注解主要用于抑制编译器警告,具体的我们简单的演示下。
- public class People {
- public void sayHello(){
- System.out.println("helo walker");
- }
- }
- public class Student extends People {
- /*@Override
- public void sayHello(){
- System.out.println("hello yam");
- }这样是没有问题的*/
- @Override
- public void say(){
- System.out.println("hello yam");
- }/*如果你定义的方法不能重写父类某个方法,要么拼写错误,参数个数,方法名不一样等,编译抛出警告*/
- }
我们需要注意的是,这里的 override 注解只能用于修饰方法,不能用于修饰类或者域。
- public class Student extends People {
- @Deprecated
- public void say(){
- System.out.println("hello yam");
- }
- }
- //调用过时方法
- public static void main(String[] args){
- Student s = new Student();
- s.say();
- }
虽然编译时抛出了警告,但是程序依然可以正常的运行结束。此注解只是告知用户被标记的方法或者类已经不再推荐使用,但是你依然是可以使用的。之所以建议不再使用,一定是有了更好的取代物了,如果你一定要在你的项目中使用,等待新的 jdk 版本发布之后,很可能删除了这些方法或者类,可能会导致你的项目原先的一些方法或者类无法识别。
- @SuppressWarnings("deprecation")
- public static void main(String[] args){
- Student s = new Student();
- s.say();
- }
例如,我们可以使用 SuppressWarnings 注解,阻止弹出过时警告。关于 SuppressWarnings 的参数主要有以下几种:
三、元注解 元注解就是用来注解注解的注解。定义可能有点绕,其实元注解是一种注解,他可以加在一般的注解上用于限制该注解的使用范围,生命周期等。一般在自定义注解时候使用的多。在 jdk 的中 java.lang.annotation 包中定义了四个元注解:
我们首先看看 @Target 的使用:
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
这是系统注解 Override 的定义源代码,我们看到 Target 注解中参数 ElementType.METHOD 表示该注解只能用于修饰方法。使用 Target 注解限定了 Override 的修饰范围只能使方法,不能是类或者域。Target 还有一些其他的参数:
通过上述的参数我们可以在定义一个注解的时候限定他的作用范围。
下面看看 Retention 这个元注解,依然以注解 Override 为例,
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
我们看到 Retention 中使用了参数 RetentionPolicy.SOURCE,这个参数表示该注解只在源代码中有效,进过编译之后将会被丢弃。还有一些其他参数:
接着使注解 Documented,这是一个关于文档的元注解,被它注解的注解在注解其他方法或者类的时候可以被 Javadoc 等工具文档化,对于一般的注解,在 Javadoc 等工具文档化类或者方法的时候会丢弃注解内容,使用它就可以使得文档化的时候依然保存着注解代码。
- //Test是一个被元注解Documented修饰
- public class User {
- @Test(value = 10,description = "do something")
- public void test1() {
- }
- }
使用 Javadoc 生成 API:
类 User 中的方法 test1 方法的头部是保留着注解的,如果是一般的注解则不会保留。
最后是元注解 Inherited,我们知道如果一个普通的注解修饰了一个父类,那么他的子类是不能继承修饰父类的注解的。
- @Deprecated
- public class People {
- public void sayHello(){
- System.out.println("helo walker");
- }
- }
- public class Student extends People {
- public void say(){
- System.out.println("hello yam");
- }
- }
我们可以看到,在父类 people 上使用了注解 Deprecated,people 类名上是有删除线的(粘贴到此处并没有显示)表示此类不推荐使用,但是我们可以看到在子类 Student 上是没有删除线的,也就是父类废弃了,子类依然是正常的。(注解不会被继承),但是如果我们希望子类能够继承父类的某些注解,那么只需要在定义该注解的时候使用我们的元注解 Inherited 修饰即可。
四、自定义注解 以上我们看到的标准注解,元注解都是 jdk 中定义好了的,如果我们想要自定义一个自己的注解就需要通过 @interface 来定义一个全新的注解。
- //定义一个注解
- public @interface myAnnotion {
- }
使用 @interface 定义一个注解的时候,会自动继承 java.lang.annotation.Annotation 接口,以下是其中的一些方法:
- boolean equals(Object obj);
- int hashCode();
- String toString();
- Class<? extends Annotation> annotationType();
我们自定义的注解,除了多了个 @符号,其他的和定义一个接口是一样的,所以这些方法我们不用实现。以上我们定义的是一个没有注解体的一个注解,像这样的注解我们叫做标记注解,这是表示一种标记,编译器根据某个类或方法是否具有此标记来判断是否要添加一些代码或做一定的检测操作。例如:@Override 注解就是一个标记注解,如果某个方法前被修饰了此注解,编译器在编译时会找到父类,判断对应的方法是否完成了重写的格式。 下面声明了一个具有注解体的注解:
- public@interface myAnnotion {
- String name()
- default "";
- int age();
- }
我们说过,声明注解和声明接口很是类似,所以注解中的所有参数都必须以抽象方法的形式存在,例如上面一样。接下来我们看如何使用该注解:
- @myAnnotion(name = "walker",age=10)
- public class Test_ann {
- public static void main(String[] args){
- }
- }
之前我们说过,注解本身不会起到任何作用,需要配合注解处理器才能发挥一定的作用,自己本身其实更像是一种特殊的注释。在上例中,我们可以在()中为注解的内部参数赋值,需要注意的是,注解的参数不允许为 null,也就是在使用注解的时候,内部的每个参数都是必须要有数值的,要么在定义的时候给赋上默认值(使用 default 关键字),要么在()内显式的赋值。允许的注解参数类型有:
如果我们想要表示注解中某个参数不存在,该怎么办呢?比如我们用上述自定义的注解去修饰了一个 People 类,如果此人的 age 不知道,我们该如何赋值(参数的值不能为 null)。我们往往用一些特殊值来标记某个参数不存在的情况,例如我们可以给 age 赋值 - 1 表示此人年龄不详,在使用注解处理器读取的时候发现 age 等于 - 1,我们就知道此人年龄不详。往往字符串类型的参数用 "" 表示参数不存在,整型类型参数使用负数表示参数不存在。 五、使用注解处理器响应注解 我们说过一个注解被定义出来之后,是不能完成任何作用的,如果没有注解处理器响应的注解和注释差不多。本小节我们看看如何定义一个注解处理器来对我们自定义的注解进行响应。还有一个前提是:我们的注解处理器实际上也是类,所以它只有在被加载到 jvm 中才能生效,但是如果我们的注解的生命周期范围到不了 jvm 的话,注解处理器也是没用的。 Java 扩充了其反射机制,使得我们可以利用反射来获取注解信息。反射中的 Class,Method,Constructor,Field,Package 都继承了接口 AnnotatedElement,这个接口主要有以下几个方法:
- /*判断是否存在指定的注解*/
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
- /*获取指定的注解*/
- <T extends Annotation> T getAnnotation(Class<T> annotationClass);
- /*获取当前元素的所有注解*/
- Annotation[] getAnnotations();
- /*返回直接存在于此元素上的指定的注解,忽略继承,如果没有返回null*/
- <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
- /*返回直接存在于此元素上的所有注解,忽略继承*/
- Annotation[] getDeclaredAnnotations();
下面看一个注解的简单总和实例:
- @Target(value = {
- ElementType.FIELD
- }) //修饰Filed的注解
- @Retention(value = RetentionPolicy.RUNTIME) //运行时保留
- public@interface PName {
- String name()
- default "";
- }
- @Target(value = {
- ElementType.FIELD
- }) //修饰Filed的注解
- @Retention(value = RetentionPolicy.RUNTIME) //运行时保留
- public@interface PAge {
- int age()
- default 0;
- }
- @Target(value = {
- ElementType.METHOD
- }) //修饰method的注解
- @Retention(value = RetentionPolicy.RUNTIME) //运行时保留
- public@interface SayHello {
- String content()
- default "hello";
- }
- public class People {
- @PName(name = "people")
- private String name;
- @PAge(age = 20)
- private int age;
- @SayHello(content = "hello people")
- public void sayHello(){
- System.out.println("hello people");
- }
- }
- public static void main(String[] args) throws NoSuchMethodException {
- //获取people类中所有注解信息
- Field[] fields = People.class.getDeclaredFields();
- for(Field f : fields){
- //遍历每个属性
- if(f.isAnnotationPresent(PName.class)){
- PName pn = f.getAnnotation(PName.class);
- System.out.println(pn.name());
- }else{
- PAge pa = f.getAnnotation(PAge.class);
- System.out.println(pa.age());
- }
- }
- Method md = People.class.getMethod("sayHello");
- SayHello sh = md.getAnnotation(SayHello.class);
- System.out.println(sh.content());
- }
上述的代码完成了将 people 类中所有注解信息全部获取打印的工作。这个例子可能不能准确的描述注解在我们程序中的作用(起码注解不会用来干这个),但是在一方面演示了定义到使用注解的过程,希望对大家在项目中实际使用有所启发。
最后,本篇文章结束了,望大家多多留言交流,相互学习。
来源: http://www.cnblogs.com/yangming1996/p/6727528.html