黑马程序员——注解

来源:互联网 发布:购物中心软件下载 编辑:程序博客网 时间:2024/04/30 01:20
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


1  注解简介

        注解(Annotation,也称为元数据)也是JDK1.5版本中添加的新特性,它为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。

        注解在一定程度上是在把元数据和源代码文件结合在一起,而不是保存在外部文档中这一大的趋势下所催生的。同时,注解也是对来自像C#之类的其他语言对Java造成的语言特性压力作出的一种回应。

        注解可以提供用来完整地描述程序所需的信息,而这些信息是无法用Java来表达的。因此,注解使得我们以编译器能够测试和验证的格式,存储有关程序的额外信息。注解可以用来生成描述文件,甚至或是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,我们可以将这些元数据保存在Java源代码中,并利用annotation API为自己的注解构造处理工具。同时,注解的优点还包括:更加干净易读的代码以及编译器类型检查等(以上内容均摘自《Java编程思想》,中文第四版,机械工业出版社)。

        由于注解的上述功能和优点,该特点在实际开发中得到了越来越广泛的应用,EJB、Spring、Structs2等JavaEE框架中都逐渐基于注解进行开发。

2  简单应用

        Java标准类库java.lang包中预定了三种注解,我们首先通过对这三种注解的简单应用,来对注解产生感性认识,帮助我们进一步对注解进行学习。这三种注解分别是:

@Override:表示当前的方法定义将覆盖父类中的方法。如果你不小心拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。

@Deprecated:该注解表示被修饰方法已过时,可以将该注解应用于那些有BUG的方法中,提示程序员尽可能不要使用这些方法,否则编译器会发出警告信息,但是可以完成编译操作的。

@SuppressWarnings:关闭不当的编译器警告信息。如果在方法A中需要调用被@Deprecated注解修饰的方法,那么可以在方法A上定义@SuppressWarnings,则编译器不会发出任何警告。

除了以上这三种注解以外,Java还提供了其他四种注解,专门用于新注解的创建。我们将在后面的内容中对它们进行介绍。

 

小知识点1:

        Java中的类名和属性名,应尽可能定义为名词,或者形容词+名词的形式,而方法名要尽可能定义为动词,或者动词+名词的形式。比如,我们可以将类名定义为“ReflectTest”,而不能定义为“TestReflect”。

 

2.1  @Deprecated——过时

代码1:

public class AnnotationTest { public static void main(String[] args) {//以下System类静态方法已过时System.runFinalizersOnExit(true);}}
代码说明:

        (1)  System类的静态方法runFinalizersOnExit方法是已过时方法,也就是在新版本JDK中被@Deprecated注解所修饰(大家有兴趣可以查阅该方法的源代码),因此在编译时,会提示“The method runFinalizersOnExit(boolean)from the type System is deprecated.”在eclipse开发工具中,如果像以上代码那样调用runFinalizersOnExit方法,方法名将直接被一条黑线覆盖,更加直接地表示该方法已过时。

        (2)  如果在命令行中对以上代码进行编译,将会出现如下提示,

注: AnnotationTest.java使用或覆盖了已过时的API。

注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。

虽然给出了警告,但依旧可以完成编译操作。如果像进一步了解具体哪个方法已经过时,可以在编译时加入“-Xlint:deprecation”命令,则会给出如下提示,

AnnotationTest.java:4: 警告:[deprecation] System中的runFinalizersOnExit(boolean

)已过时

                System.runFinalizersOnExit(true);

                      ^

1 个警告

此时就是准确告知用户,是哪个方法已经过时。

        我们也可以自定义一个过时方法,如下代码所示,

代码2:

public class AnnotationTest2 {public static void main(String[] args) {fun();} //将fun方法定义为过时方法@Deprecatedpublic static void fun(){System.out.println("HelloWorld!");}}
以上代码由于仅仅使用@Deprecated对方法进行了修饰,但并没有定义任何警告信息,因此如果在命令行中手动编译时,不会给出任何警告。但是在eclipse开发工具中,fun方法还是会被黑线覆盖。

        可能有朋友会问,为什么过时的或者作废的方法,不是直接在源代码中删除掉,而是使用注解来提示程序员呢?因为这一类方法,很可能在一些旧的项目中使用,如果直接删除掉这些方法,那么一旦程序员更新了JDK版本以后,就会由于无法调用过时方法,而使整个项目都无法正常运行。因此使用注解是一种折衷的方法,既可以令曾经调用过时方法的项目正常运行,同时也可以提示后面的程序员尽可能不要使用过时方法。

2.2  @SuppressWarnings——压缩警告

        在编辑代码的时候,如果已经知道将要调用的某个方法是已过时方法,因此不再需要编译器进行提示,此时可以使用@SuppressWarnings注解。

代码3:

public class AnnotationTest2 {//使用以下注解,可以去掉编译器的警告提示@SuppressWarnings("deprecation")public static void main(String[] args) {System.runFinalizersOnExit(true);}}
使用命令行对以上代码进行编译时不再提示任何警告了。那么说明@SuppressWarnings注解的作用就是去掉编译器对过时方法的警告提示。

2.3  @Override——方法覆盖

        我们首先复习一下,覆盖方法的几点注意事项:

(1)  子类(或者实现类)的方法返回值类型、方法名以及参数列表,要与父类(或接口)的方法定义完全相同。

(2)  子类方法的访问权限要大于等于父类。

(3)  静态方法只能由静态方法覆盖。

(4)  父类的方法若是私有方法,将无法被子类覆盖。即使子类定义了一个非私有的同名方法,也只是相当于定义了一个全新的方法。

(5)  子类方法只能抛出与父类方法完全相同的异常,或者是这些异常的子类异常,或者是这些异常中的一部分。不能抛出父类方法中没有抛出的异常。

        那么在覆盖方法时,只要不满足以上任意一条,覆盖操作就会失败,换句话说,子类所谓的覆盖的的方法实际就是定义了一个全新的方法。但是由于覆盖失误并非是语法错误,因此编译时期,编译器并不会报出任何错误提示,甚至在运行时期也不会出现任何问题,因此如果程序执行结果不是预期结果时,就会很难排查错误。有时候,这种错误可能就是因为拼错了一个字母而导致的。

        而@Override注解实际就是起到从语法角度,帮助程序员发现覆盖操作时的失误。如果一个方法被@Override修饰,那么编译器在编译时就会从语法角度严格检查被修饰方法有没有完全遵守上述规则,如果不满足条件就会在编译时期给出警告提示。我们以复写Object类的equals方法(在覆盖该方法时,很容易出现错误)为例进行演示,

代码4:

public class Person {private String name;private int age;      public Person(String name, int age) {super();this.name = name;this.age = age;} //在复写equals方法时,很容易将参数类型定义本类类型,如下所示public boolean equals(Person obj) {if(this == obj)return true;if(obj == null)return false;if(getClass() != obj.getClass())return false;Person other = (Person) obj;if(age != other.age)return false;if(name == null) {if(other.name != null)return false;}else if (!name.equals(other.name))return false;return true;}}
代码说明:

        在复写equals方法时,很容易将参数类型定义为本类类型,如果没有使用@Override注解进行修饰,那么编译期间不会给出任何提示,因为这并非是任何语法错误。但是如果需要用到子类中的equals方法按照指定条件判断两对象相同时(比如,向HashSet集合中存储对象时),由于子类并未复写父类的方法,因此未能调用子类的方法,最终导致出现预期以外的结果(比如,不能保证元素的唯一性)。因此我们对以上代码进行修改,将equals方法使用@Override注解进行修饰,如下代码所示,

代码5:

//只给出了方法声明部分代码,其他代码省略public class Person {//--- @Overridepublic boolean equals(Person obj) {//---}
eclipse开发工具在编译以上代码时,将给出以下提示:The method equals(Person) of typePerson must override or implement a supertype method,意思是Person类的equals(Person)方法必须复写或者实现父类的方法。换言之,由于代码5中的equals方法没有严格遵守方法覆盖规则,因此提示覆盖失败。

        如果将以上代码使用命令行进行编译,则会给出如下警告提示:

Person.java:13: 错误: 方法不会覆盖或实现超类型的方法

       @Override

        ^

1 个错误

可见,@Override注解确实会对覆盖语法进行检查,如果未能严格遵守覆盖规则,则会给出错误提示,并且编译失败。

2.4  总结

        那么从以上代码的演示中可以看出,注解相当于一种标记,为方法(或者类,后面会讲到)加了注解就等于打上了某种标记。那么这个标记的作用告诉编译器一些信息(可以是方法相关信息,或者类相关信息等),那么java编译器、开发工具或者其他软件就可以通过反射获取到这些信息,从而根据这些信息,在编译时期(也可能是在运行时期,后面将会说明)采取相应的措施,并给出相关提示。

        注解不仅可以定义在方法上,还可以定义在包、类、字段、方法的参数以及局部变量上,在不同的位置上根据不同的定义内容就可以发挥不同的作用。我们在简单了解了注解的作用以后,就可以尝试自定义注解了,下面我们就来介绍如何自定义注解。

3  自定义注解

        实际上,每一个注解其实就是一个类,使用注解修饰一个方法,实际就是创建了一个注解的实例对象,如果我们想要使用自定义注解,那么首先就要像定义一个类那样定义一个注解。在介绍自定义注解之前,我们先来说一说,注解的应用体系。

3.1  注解的应用体系

        假设我们自定义了一个注解类A,那么我们可以将注解修饰到一个类B上。那么当类C使用类B时,就可以通过反射的方式,对类B进行操作,获取到类B上注解的信息,下图即反映了这一注解的应用体系。


如图所示,我们自定义了一个注解类A,将注解类A修饰到类B上,相当于类B使用了注解类A。当又有另一类C使用了类B时,我们可以通过反射的方式来检测类B是否定义了注解。以上就是比较完整的一个注解应用体系,因此下面我们在演示自定义注解的时候,也需要定义三个类。

3.2  自定义注解类

1)  定义格式与相关方法简介

        自定义注解的定义格式如下:

public @interface Annotation { }
定义注解的关键字是“@interface”,由于注解相当于一个类,因此访问权可以是public和默认的包访问权限。紧随关键字后的就是注解的名称。下面我们就自定义一个名为“ItcastAnnotaion”的注解类,并将其修饰到一个名为Demo的类上,进而通过一个测试类,利用反射来对Demo类上的注解进行操作。

代码6:

//自定义注解类@interface ItcastAnnotation { }//应用了注解类的类@ItcastAnnotationclass Demo { }//对应用了注解类的类进行反射操作的类public class AnnotationTest3 {public static void main(String[]args) {if(Demo.class.isAnnotationPresent(ItcastAnnotation.class)){ItcastAnnotationannotation = (ItcastAnnotation)Demo.class.getAnnotation(ItcastAnnotation.class);System.out.println(annotation);}}}
执行以上代码,在控制台中并没有打印任何内容。

代码说明:

        (1)  代码6中首先定义了一个最简单注解类,然后将该注解修饰到了自定义类Demo上,最终在测试类AnnotationTest3中,利用反射对Demo类上的注解进行操作。

        (2)  其中isAnnotationPresent方法是定义在Class类上的,其API文档如下:

        public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):如果指定类型的注释存在于此元素上,则返回true,否则返回false。此方法主要是为了便于访问标记注释而设计的。也就是说,该方法用于判断此Class对象(调用该方法的Class对象)对应的类上是否使用了指定的注解类进行修饰。所传的参数就是,指定注解类对应的Class对象的。

        (3)  getAnnotation同样定义于Class类中,用于返回定义于此Class对象上的指定注解对象,其API文档为:

        public <A extends Annotation> A getAnnotation(Class<A> annotationClass):如果存在该元素指定类型的注释(也即注解),则返回这些注释,否则返回null。由于注解类是一种特殊的类,因此同样可以创建其实例对象的,就像上文所述,在类、方法或其他地方应用注解进行修饰,实际就是创建了一个注解对象,那么getAnnotation方法就是获取这一对象并返回。

2)  注解的生命周期

        以上方法虽然能够正常执行,但并没有显示任何结果。如果打印if语句的判断,其结果是false,换句话说,if语句内的代码根本就没有执行。明明Demo类上定义了@ItcastAnnotation注解,为什么isAnnotationPresent返回值是false呢?

        这涉及到注解的生命周期的问题。如果我们将注解类ItcastAnnotation进行如下修改,那么测试类AnnotationTest3的执行结果将会不同。

代码7:

import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy; //在自定义注解上定义了一个注解@Retention(RetentionPolicy.RUNTIME)@interface ItcastAnnotation { }
再次执行测试类的结果为:

@ItcastAnnotation()

执行结果打印了ItcastAnnotation注解的类名,说明该注解确实定义在了Demo类上。

代码说明:

        (1)  我们在自定义注解ItcastAnnotation上又修饰了一个名为Retention的注解,这其实也是Java的一个预定义注解,专门用于自定义注解的创建,该预定义注解位于Java标准类库java.lang.annotation包中。该包中还有其他三个预定义注解类,分别是Documented、Inherited、Target,它们都是用于自定义注解的创建,我们将在后面的内容中进行介绍。

        (2)  在注解上定义的注解称为元注解。那么代码7中Retention就是一个元注解。

        (3)  Retention注解的作用实际是在设置被修饰注解的生命周期。正如前文所说,注解就相当于一个标记,用于告诉编译器被修饰对象的一些信息,而编译器读取到这些信息,并作出相应的措施以后,实际上注解的使命就已经完成了,没有了继续存在的意义,因此在默认情况下编译器在编译的时候,会将源代码中的注解保留在“.class”文件中,但Java虚拟机在加载修饰有注解的类文件时,就会去掉注解信息,因此在运行时也就不可能在Class对象上查找到任何注解信息了,这也就是执行代码6时没有任何执行结果的原因。注解声明周期的机制有些类似于泛型的类型擦除,只不过两者的发生时间不同。

        注解的生命周期除了保持在“.class”文件中,还可保持在源代码中,或者保留到运行时,这就需要通过枚举RetentionPolicy的三个常量元素进行设置,分别是RetentionPolicy.CLASS、RetentionPolicy.SOURCE,以及RetentionPolicy.RUNTIME。以下是枚举RetentionPolicy的API文档,

说明:

        注释保留策略。此枚举类型的常量描述保留注释的不同策略。它们与Retention元注释类型一起使用,以指定保留多长的注释。

枚举常量摘要:

        CLASS:编译器将把注释记录在类文件中,但在运行时VM(虚拟机)不需要保留注释。假如注解A被设置为CLASS常量,而类B又被注解A所修饰,那么注解A将保留在B类的“.class”文件中,但是Java虚拟机在加载类B时,还是会去掉其中的注解,因此在运行期间还是不能获取注解的信息。

        RUNTIME:编译器将把注释记录在类文件中,在运行时VM将保留注释,因此可以反射性地读取。被RUNTIME修饰的注解类,不仅保留在“.class”文件中,而且加载到内存时,还能继续保留在Class对象内,因此可以反射地获取到注解信息。

        SOURCE:编译要丢弃的注释。被SOURCE修饰的注解,在编译期间将直接被编译器去掉。

根据以上信息,也就了解了为什么ItcastAnnotation注解被Retention元注解修饰时,如果设置的常量为RUNTIME,则能够在运行期间获取到ItcastAnnotation注解对象的原因了。

        注意到在修饰Retention注解时,是将RetentionPolicy的常量元素传递到了一对括号内,这一格式很像是创建一个对象时,通过构造方法进行初始化的操作,这也就再次说明注解实际就是一种特殊的类。

3)  三个预定义注解的声明周期

        了解了注解的生命周期机制,我们可以通过分析java.lang包中的三个预定义注解,来进一步深入对注解声明周期的人设。

        首先是@Override,这一注解的作用实际就是告诉编译器,对被修饰方法进行复写语法检查,如果发现语法错误则给出错误提示。但是通过编译以后,这一注解的作用也就没有了,因此@Override注解的生命周期仅保留到源代码,编译时去掉。

        @SuppressWarnings注解同样也是告诉编译器,检测到过时方法时,不必进行警告提示。而编译过后,同样失去了作用,因此该注解的生命周期同样是源代码阶段。

        @Deprecated注解同样也是告诉编译器被修饰对象已过时,因此该注解看似也是保留到源代码中。但是假如定义在类A中的fun方法被@Deprecated注解所修饰,而fun方法又在B类的bar方法中所调用时,编译器时如何发现fun方法的过时的呢?以下代码体现了上述调用关系,

代码8:

class A{@Deprecatedpublic static void fun(){}}public class B{public void bar(){A.fun;}}
在类B的bar方法中,调用fun方法时,并没有定义任何注解,但是编译器依旧可以提示fun已过时的警告信息,即使类A和类B分别定义在两个“.java”文件时也是一样的。这是因为,编译器除了能够对源代码进行检查以外,还能够对“.class”文件进行检测。当编译代码8中类B的源代码时,编译器将自动在classpath路径下搜索类A对应的“.class”文件,将其加载到内存中,转换为Class对象以后,去检测此Class对象,进而通过注解去判断fun方法是否已过时,因此@Deprecated注解是可以保留到运行时的,也就是“RUNTIME”阶段。其实这也可以通过查阅Retention注解类的API文档了解。

3.3  另一个预定义注解——@Target

        以上内容中我们通过引入注解的声明周期,介绍了用于修饰自定义注解的元注解之一@Retention,下面再介绍一个预定义元注解——@Target。

API文档说明:

        指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在Target元注释,则声明的类型可以用在任意程序元素上。如果存在这样的元注释,则编译器强制实施指定的使用限制。

        通过以上说明可知,可以通过@Target注解,指定被修饰注解可以用于修饰什么类型的元素,比如类、接口、枚举、方法、字段、构造方法、局部变量、包、参数,亦或是其他注解类。我们继续使用前述ItcastAnnotaion类来对@Target注解进行演示。

代码9:

import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType; @Retention(RetentionPolicy.RUNTIME)//将ItcastAnnotation注解定义为,只能用于修饰方法@Target(ElementType.METHOD)@interface ItcastAnnotation { }//以下注解将发生编译错误//@ItcastAnnotationclassDemo { }
以上代码将发生编译时错误,因为ItcastAnnotaion注解已经被明确定义为只能用于修饰方法,但却被用来修饰Demo类了,因此产生了编译错误。

代码说明:

        与Retention注解类似,在定义@Target注解时,也需要传递一个枚举常量元素来指定该注解的功能,而@Target注解能够接收的枚举类型为ElementType。ElementType同样位于java.lang.annotation包中,对外提供了八种常量元素,用于指定不同的被修饰对象,具体内容可以参考ElementType的API文档。

        通过传递多个ElementType常量元素,可以令被@Target注解修饰的注解类用于修饰多种不同的元素,对代码9进行修改后的代码如下,

代码10:

import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType; @Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})@interfaceItcastAnnotation { }@ItcastAnnotationclassDemo { }
再次编译,不再提示任何编译错误。

代码说明:

        注意到,在@Target后的一对括号内又包含了一个对大括号,这相当于是一个ElementType的数组,分别容纳了METHOD和TYPE两个枚举常量,这就表示ItcastAnnotation既可以修饰方法又可以修饰类、接口以及枚举类型。

3.4  注解的属性

        在定义注解时除了可以为注解定义元注解以外,还可以在在注解类内部定义属性,就像普通类的成员(注解的属性更像是一个成员方法)。通过为注解定义属性,可以大大丰富注解的功能。实际上@Retention和@Target在注解修饰其他注解时,后接一对括号,并传递枚举的常量元素时,就是通过注解类内部的一个属性去接收的,编译器检测到这两个注解后,就会根据不同的属性值(比如ElementType.METHOD和ElementType.TYPE,以及RetentionPolicy.SOURCE和RetentionPolicy.RUNTIME)采取不同的操作。下面我们就来介绍如何自定义属性,以及可以注解属性的使用方法。

1)  自定义属性

        我们继续以ItcastAnnotatioin注解为例,

代码11:

import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType; @Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})@interface ItcastAnnotation {//在注解内部定义了一个名为color的属性/*public abstract*/ Stringcolor();}/*在使用ItcastAnnotation修饰Demo类的时候,通过以下格式指定color属性的值*/@ItcastAnnotation(color = "red")class Demo { }public class AnnotationTest4 {public static void main(String[] args) {if(Demo.class.isAnnotationPresent(ItcastAnnotation.class)){ItcastAnnotationannotation = (ItcastAnnotation)Demo.class.getAnnotation(ItcastAnnotation.class);System.out.println(annotation.color());}}}
执行结果为:

red

代码说明:

        (1)   注解的定义更像是一个接口(这其实从定义注解的关键词“@interface”就可以看出来),当我们为注解定义一个属性时,相当于在接口中定义了一个公有抽象方法,因此上述代码中color属性前的“publicabstract”修饰符是可以去掉的。String表示这是属性的返回值类型。

        (2)  在使用注解的属性时,就会在注解名后接一对括号,并根据“属性名=属性值”的格式,为属性赋值,为属性赋值的类型必须与属性的返回值类型相同。

        (3)  在测试代码中获取到ItcastAnnotation注解对象以后,就可以像调用方法那样调用color属性,则其返回值就是在Demo类上定义ItcastAnnotation注解时,指定的属性值。

        (4)  可以在一个注解类中,定义多个属性,那么使用该注解修饰其他类时,就需要为每个注解都指定属性值,不同属性之间使用“,”号分隔。

2) value——特殊属性

        当使用某个注解去修饰某个类时,如果仅仅指定一个名为value的属性值时,可以不指定属性名,直接在注解名后接的一对括号中传递属性值即可,这是Java默认语法格式。比如代码3中的“@SuppressWarnings("deprecation")”,以及代码7中的“@Retention(RetentionPolicy.RUNTIME)”,在定义这两个注解时,属性值的传递并没有指定属性名,这就表示传递的实际参数直接赋值给了value属性。但是,在定义自定义注解时,若要使用value属性,则必须在其内部显示定义一个属性名为value,返回值类型为String的属性,因为value属性并非是默认存在的属性。如果需要同时指定包括value在内的多个属性时,就必须要指明value的属性名了,如下代码所示,

代码12:

import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType; @Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})@interface ItcastAnnotation {String color();String value();//定义value属性}//同时指定color和value属性的属性值@ItcastAnnotation(color = "red", value = "abc")class Demo { }public class AnnotationTest5 {public static void main(String[] args) {if(Demo.class.isAnnotationPresent(ItcastAnnotation.class)){ItcastAnnotationannotation = (ItcastAnnotation)Demo.class.getAnnotation(ItcastAnnotation.class);//同时获取ItcastAnnotation注解对象color和value的属性值System.out.println(annotation.color());System.out.println(annotation.value());}}}
执行结果为:

red

abc

大家需要注意的是,如果仅指定value属性的属性值时,value属性名是必须要省略的,这是固定的语法格式,如果在这种情况下,还是手动指定属性名将会提示编译错误。

        也可以为属性值指定默认初始化值,这样就不必在修饰类的时候指定属性值了,默认初始化代码格式如下,

代码13:

impor tjava.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType; @Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface ItcastAnnotation {String color() default "green";//指定color属性的默认初始化值为"green"String value();}//由于为color属性指定了默认初始化值,因此只需要指定value的属性值即可@ItcastAnnotation("abc")public class Demo { }
代码说明:

        如果要为注解类的属性指定默认初始化值,就要使用“default”关键词。由于指定了color属性的默认初始化值,因此在使用ItcastAnnotation注解修饰其他类的时候,由于仅需要指定value的属性值,因此必须省略属性名。

        当然,即使通过“default”关键字定义了某个属性的默认初始化值,在使用注解时还是可以指定一个属性值来覆盖默认初始化值。

3)  属性的类型

        在上述内容中,我们为自定义注解——ItcastAnnotation定义了返回值类型为字符串的属性,而预定义注解中定义了返回值类型为枚举类型的属性。那么除了这两种类型以外,我们还可以定义8中基本数据类型、Class类型、枚举、注解,以及存储有前述这几种类型元素的数组等等类型的属性,除此以外的类型是不能作为注解的元素的,否则将提示编译错误。我们将通过下面的代码演示其中几种类型属性的定义。

代码14:

//自定义元注解类public @interface MetaAnnotation {//唯一的属性String value();}//自定义注解类import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.lang.annotation.ElementType; @Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface ItcastAnnotation {String color() default "green";String value();int[] arrayAttr();//数组类型属性TrafficLamp lamp() default TrafficLamp.YELLOW;//枚举类型属性MetaAnnotation annotationAttr();//注解类型属性Class clazz() default ItcastAnnotation.class;//Class类型属性}//应用了自定义注解类的类@ItcastAnnotation(value = "abc",arrayAttr = 100,//为注解类型属性指定属性值annotationAttr = @MetaAnnotation("MetaAnnotation"))public class Demo { }//对应用了自定义注解类的类进行反射操作的测试类import java.util.Arrays; public class AnnotationTest6 {public static void main(String[] args) {if(Demo.class.isAnnotationPresent(ItcastAnnotation.class)){ItcastAnnotationannotation = (ItcastAnnotation)Demo.class.getAnnotation(ItcastAnnotation.class);System.out.println(annotation.color());System.out.println(annotation.value());System.out.println(Arrays.toString(annotation.arrayAttr()));//获取数组类型属性值System.out.println(annotation.lamp());//获取枚举类型属性值System.out.println(annotation.annotationAttr());//获取注解类型属性值System.out.println(annotation.clazz());//获取Class类型属性值}}}
执行结果为:

green

abc

[100]

YELLOW

@ MetaAnnotation(value=MetaAnnotation)

interface ItcastAnnotation

代码说明:

        (1)  为ItcastAnnotation注解定义了数组类型的属性,注意到指定arrayAttr属性值时,并没有遵守传统的数组格式——没有定义大括号,而是直接将唯一的数组元素100赋值给了一个数组。这是为了方便代码书写,而进行的简化。如果注解中定义有数组类型的属性,并且指定的数组属性值中只包含一个元素时,可以省略大括号。

        实际上这一特性也体现在了@Target注解的使用上。实际上,@Target注解在修饰其他类时,是需要传递一个ElementType类型的数组的,比如代码10。但是,如果数组中只有一个元素时就可以省略大括号,比如代码9。

        (2)  注解中的属性类型还可以是注解。为演示注解类型的属性,我们自定义了一个元注解类MetaAnnotation,并为其定义了字符串类型的属性value。接着在ItcastAnnotation注解中定义了属性名为annotationAttr,属性类型为MetaAnnotation注解的属性。那么最终将ItcastAnnotation修饰在Demo类上时,首先要为ItcastAnnotation的属性annotationAttr指定属性值——创建一个注解对象——“@MetaAnnotation”,接着还要为MetaAnnotation注解中的value属性指定属性值,因此后接一对括号,并传递了一个字符串。那么最终执行测试类的结果,除了带着value的属性值打印了元注解MetaAnnotation注解的类名。

        关于注解语法更为详细的介绍,可以参考Java语言官方语法规范,最新版本为《The Java Language Specification_Java SE 8 Edition》。

0 0
原创粉丝点击