一、Annotation的简单介绍
1、什么是Annotation?
Annotation其实就是代码里的一种特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。
2、Annotation的好处:
通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。
JDK1.5新特性中的Annotation也增加了Java对元数据(MetaData)的支持。
3、Annotation的特点:
Annotation可以像修饰符一样被使用,可以用于修饰包、类、构造方法、一般方法、成员变量、局部变量以及参数的声明。
4、Annotation的基本使用
使用Annotation时需要在其前面加上“@”符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素。
5、系统内建三个的Annotation:
(Annotation定义在java.lang包当当中,因为此包在使用时是自动导入的,所以可以直接使用内建的3个Annotation)
@Override:限定重写父类方法,该注解只能用于方法。
@Deprecated:用于表示某个程序元素(类、方法、变量等等)已经过时。
@SuppressWarnings:抑制编译器的安全警告。
代码示例:
package cn.itcast.day2;
public class AnnotationTest {
@SuppressWarnings("deprecation") //该Annotation标识表示忽略抑制编译器的警告。
public static void main(String[]args) {
System.runFinalizersOnExit(true);
}
@Deprecated //该Annotation标识表示此方法已经过时。
public static void sayHello(){
System.out.println("hello world");
}
@Override //该Annotation标识表示次方法是重写了父类的方法。
public String toString(){
return "";
}
}
二、系统内建的Annotation介绍
1、@Override
作用:主要是在方法覆写时使用,用于保证方法覆写的正确性(即保证方法是否被正确覆写)。
@Override代码示例:
package com.hy.test;
class Person{
public String getInfo(){
return "person run";
}
}
class Student extends Person{ //Student类继承Person类
@Override //此处明确标识出方法覆写的操作。
public String getInfo(){
return "student run ...";
}
}
public class OverrideAnnotationDemo {
public static void main(String[]args) {
//输出信息。
System.out.println(newStudent().getInfo());
//输出结果:student run …
}
}
如果覆写方法错误,则在编译时会出现以下错误:
因为@Override是标识方法覆写的正确性,而子类中的getinfo()和父类中的getInfo()方法是两个不同的方法,不是真正的方法覆写,所以在编译时会出现以上错误提示。
注意:@Override在使用时只能在方法上使用。
2、@Deprecated
作用:主要功能是用来声明一个已过期的方法(即不建议使用的方法)。
@Deprecated代码示例:
package com.hy.test;
class Demo{
@Deprecated //标识声明该方法已经过期,不建议使用。
public String getInfo(){
return "hello java";
}
}
public class DeprecatedAnnotationDemo {
public static void main(String[]args) {
System.out.println(new Demo().getInfo());
}
}
注意:如果在程序中使用了此方法,则在编译时会出现警告信息。虽然有警告信息,但是程序依然可以正常运行,该方法可以继续使用。
还可以根据警告信息里面提示的命令参数来获取已过时的方法以及位置。
3、SuppressWarnings
作用:主要功能是用来抑制编译器的的警告。
代码示例1:抑制单个警告
package com.hy.test;
public class AnnotationTest {
@SuppressWarnings("deprecation") //单个警告:抑制划横线的方法已过时提示的警告。
public static void main(String[] args) {
System.runFinalizersOnExit(true);
}
}
注意:
如果要同时抑制多个警告的话,则可以在@SuppressWarnings后面通过字符串数组的形式增加多个注释关键字进行声明。
代码示例2:抑制多个警告
package com.hy.test;
@Deprecated
class Test<T>{ //定义泛型类,并标识此类已经过期。
private T var;
public T getVar() {
return var;
}
public void setVar(T var) {
this.var = var;
}
}
public class AnnotationTest {
@SuppressWarnings({"unchecked","deprecation"}) //同时抑制两条警告。
public static void main(String[]args) {
Test t = new Test();
t.setVar("hello world");
System.out.println(t.getVar());
}
}
注意:
在设置注释信息的时候,是以“key-value”的形式出现的。所以也可以在@SuppressWarnings时也可以直接使用“value={"unchecked","deprecation"}”的方式设置。
代码示例3:@SuppressWarnings的另一种设置方式
package com.hy.test;
@Deprecated
class Test2<T>{
private T var;
public T getVar() {
return var;
}
public void setVar(T var) {
this.var = var;
}
}
public class AnnotationTest2 {
@SuppressWarnings(value={"unchecked","deprecation"}) //另外一种同时抑制两种警告的方式。
public static void main(String[]args) {
Test2 t2 = new Test2();
t2.setVar("hello java");
System.out.println(t2.getVar());
}
}
三、自定义Annotation
1、自定义Annotation的声明格式:
[public] @interface Annotation名称{
数据类型变量名();
}
注意:
①、在自定义Annotation时也可以定义各种变量,但是变量后面必须要加上一对小括号()。
②、使用@interface声明Annotation接口,实际上就是相当于继承了java.lang.annotation.Annotation接口。
代码示例:自定义Annotation
package com.hy.test;
public @interface MyAnnotationNoneMethod{ //自定义Annotation
}
@MyAnnotationNoneMethod //使用自定义的Annotation
class Demo{
}
2、自定义带有参数的Annotation
Java内建的3种Annotation中的@SuppressWarnings注释中可以传递参数,而自定义的Annotation中也可以根据声明格式中的变量属性传递参数。
代码示例1:带一个参数的自定义Annotation
package com.hy.test;
public @interface MyAnnotationSingleParam{
public String value(); //记住:此处的变量名必须是value,写成其它的则会提示报错。
}
@MyAnnotationSingleParam("java") //带一个参数的自定义Annotation
class Demo{
}
代码示例2:带一个参数的自定义Annotation另一种设置方式
package com.hy.test;
public @interface MyAnnotationSingleParam{
public String value();
}
@MyAnnotationSingleParam(value="java") //另外一种设置方式。此种方式一般用于带多个参数时才会使用。
class Demo{
}
代码示例3:带多个参数的自定义Annotation
package com.hy.test;
public @interface MyAnnotationMoreParam{
public String key(); //变量的名称必须要为key或者value
public String value();
}
@MyAnnotationMoreParam(key="hello",value="java")
class Demo{
}
注意:
①、如果想要为一个Annotation变量属性设置多个内容,则必须将该变量属性定义成一个数组。
②、如果Annotation的属性只有一个值,则在使用该Annotation的时候可以省略属性名(即:Annotation的变量名)不写。
代码示例4:使自定义的Annotation单个参数属性有多个内容
package com.hy.test;
public @interface MyAnnotationArrayParam{
public String[] value(); //接收设置的内容是一个字符串数组。
}
//使自定义的单个参数属性有多个内容。
@MyAnnotationArrayParam(value={"hello","java","123"})
class Demo{
}
3、设置自定义Annotation的默认值
当自定义了一个带单个或者多个参数属性的Annotation时,如果在使用该Annotation时没有传入相应的参数内容,则在编译时候会出现报错。
代码示例:
package com.hy.test;
public @interface MyAnnotationDefaultValue{
//该自定义Annotation内定义了两个变量属性。
public String key();
public String value();
}
@MyAnnotationDefaultValue //该类使用Annotation时没有指定具体属性内容。
class Demo{
}
class MyAnnotationDefaultValueDemo{
public static void main(String[]args) {
new Demo();
}
}
错误提示:
Exception in thread "main" java.lang.Error: Unresolved compilationproblems:
Thepublic type MyAnnotationDefaultValue must be defined in its own file
Theannotation @MyAnnotationDefaultValue must define the attribute key
Theannotation @MyAnnotationDefaultValue must define the attribute value
atcom.hy.test.Demo4.<init>(MyAnnotationDefaultValueDemo.java:3)
atcom.hy.test.MyAnnotationDefaultValueDemo.main(MyAnnotationDefaultValueDemo.java:15)
结论:以上错误提示表明在定义Annotation时设置了属性,则在使用时就必须设置属性内容。
设置Annotation默认值的作用:
为了方便用户使用,可以在定义Annotation变量属性时指定其默认值,避免因为使用Annotation没有设置属性内容而出现报错。
设置Annotation默认值的声明格式:
[public] @interface Annotation名称{
数据类型变量名() default默认值;
}
代码示例:设置Annotation默认值
package com.hy.test;
public @interface MyAnnotationDefaultValue{
public String key() default "hello"; //设置变量属性的默认值内容。
public String value() default "java"; //设置变量属性的默认值内容。
}
@MyAnnotationDefaultValue //此处没有设置属性内容。
class Demo4 {
}
4、使用枚举限制Annotation设置的内容
在Annotation中可以通过枚举来限定Annotation的参数属性的取值范围,即Annotation参数属性的内容必须为枚举类中定义的元素内容。
代码示例:
package com.hy.test;
enum Technology{ //定义枚举类。
JAVA,JSP,HTML; //定义枚举类中的元素内容。
}
public @interface MyAnnotationEnum{
//设置默认值,且默认值必须为枚举中定义的取值内容。
public Technology tech() default Technology.JAVA;
}
@MyAnnotationEnum(tech=Technology.JSP) //使用自定义的Annotation,传入参数内容,参数内容为枚举类中的指定元素内容。
class Demo5{
}
5、Retention和RetentionPolicy
Retention的作用:在Annotation中,可以通过Retention定义定义一个Annotation的保存范围。
Retention的源码定义:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public@interfaceRetention {
/**
* Returns theretention policy.
* @return theretention policy
*/
RetentionPolicy value();
}
RetentionPolicy的作用:Retention的源码中存在一个RetentionPolicy类型的变量,此变量用于指定Annotation的保存范围。
RetentionPolicy的3个范围:
序 号 | 范 围 | 描 述 |
1 | SOURCE | 此Annotation类型的信息只会保留在程序源文件(.java)中,编译之后不会保存在编译好的字节码文件(.class)中。 |
2 | CLASS | 此Annotation类型的信息将保留在程序源文件和编译之后的字节码文件当中。在使用此类时,这些Annotation信息将不会被加载到JVM中去,如果一个Annotation声明时没有指定范围,则默认是此范围。 |
3 | RUNTIME | 此Annotation类型的信息保留在源文件和编译之后的字节码文件当中,在执行时也会加载到JVM中去。 |
Java内建的3个Annotation的保存范围:
@Override:定义采用的是@Retention(value=SOURCE),只会保存在程序源文件当中。
@Deprecated:定义采用的是@Retention(value=RUNTIME),保留在源文件和字节码文件当中,在执行时也会被加载进JVM。
@SuppressWarnings:定义采用的是@Retention(value=SOURCE),只会保存在程序源文件当中。
代码示例:定义在RUNTIME范围内有效的Annotation
package com.hy.test;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
enum Tech{
JAVA,JSP,HTML;
}
@Retention(value=RetentionPolicy.RUNTIME) //此Annotation在执行时会被加载进JVM
public @interface MyDefaultRetentionAnnotation{
public Tech value() default Tech.JAVA; //设置Annotation的默认值为枚举中的内容。
}
@MyDefaultRetentionAnnotation(value=Tech.HTML)
class Demo{
}
注意:
当Annotation定义在RUNTIME范围时,在程序执行时起作用,但是如果此时将其设置成其它Retention范围(CLASS或者SOURCE),则以后在Annotation的应用中肯定是无法访问到的。
要解决这个问题,则必须结合Java中的反射机制。
四、Annotation与反射的调用
1、Class类中关于Annotation的常用方法:
<A extends Annotation> A getAnnotation(Class<A> annotationClass):获取某一个元素上存在的全部注释。
Annotation[] getAnnotations():获取元素上的所有注释。
Annotation[] getDeclaredAnnotations():获取直接存在于此元素上的所有Annotation
boolean isAnnotation():判断该Class对象是否是一个Annotation类型,如果是则返回true,否则返回false。
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断一个元素上是否存在注释Annotation,如果是则返回true,否则返回false。
代码示例:获取元素上全部的Annotation
package com.hy.test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
class Demo{
@Deprecated
@Override
@SuppressWarnings("all")
public String toString(){
return "Hello Java";
}
}
public class GetAnnotations {
public static void main(String[]args) throws Exception {
Class c = Class.forName("com.hy.test.Demo");
Method method = c.getMethod("toString");
Annotation[] annotation = method.getAnnotations();
for(Annotation an : annotation){
System.out.println(an);
}
}
}
运行结果是:@java.lang.Deprecated()
问题:明明定义了3个Annotation,为什么输出结果只获取到1个?
因为Demo类中的toString()方法上虽然使用了3个Java内建的Annotation注释,但是其中只有@Deprecated使用了RUNTIME的方式声明,其它两个都是SOURCE方式,所以运行结果只取得一个。
其实,以上方法是获取到了一个元素中全部使用了RUNTIME方式所声明的Annotation。
2、通过反射获取指定的Annotation
为了获取到某个指定的Annotation,所以在获取之前需要进行明确的判断,使用Class类中的isAnnotationPresent()方法实现。
代码示例:获取指定的Annotation中的内容
//自定义一个使用RUNTIME方式声明的Annotation并且在类中使用该自定义Annotation
package com.hy.test;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
//定义一个使用RUNTIME方式声明的Annotation
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationReflect {
public String key() default "zhangsan"; //设置key的默认值。
public String value() default "20"; //设置value的默认值。
}
class Demo8{
@Override
@Deprecated
@SuppressWarnings("all")
@MyAnnotationReflect(key="lisi",value="28") //使用自定义的Annotation并设置两个属性参数的值。
public String toString(){ //覆写toString()方法。
return "Hello JAVA";
}
}
//获取指定的Annotation中的属性值。
package com.hy.test;
import java.lang.reflect.Method;
public class AnnotationReflect{
public static void main(String[]args) throws Exception{
//获取Class类的实例。
Class c = Class.forName("com.hy.test.Demo8");
//获取toString()方法。
Method method = c.getMethod("toString");
//判断自定义的Annotation上是否存在注释。
if(method.isAnnotationPresent(MyAnnotationReflect.class)){
//声明自定义Annotation的对象。
MyAnnotationReflect mar = method.getAnnotation(MyAnnotationReflect.class);
//获取自定义Annotation中指定变量参数的内容。
String key = mar.key();
Stringvalue = mar.value();
//输出获取到的Annotation变量参数的内容。
System.out.println("key="+key);
System.out.println("value="+value);
}
}
}
输出结果:key=lisi
value=28
五、其它常用注释
1、@Target注释
①、作用:如果一个Annotation没有明确的指定使用的位置,则可以在任意的位置使用(例如:可在类、方法…上面使用),而如果要指定一个Annotation的使用位置,则必须通过@Target注释。
代码示例:Annotation可以在任意位置使用
package com.hy.test;
//自定义Annotation,并指定属性变量初始值。
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationReflect {
public String key() default "zhangsan";
public String value() default "20";
}
@MyAnnotationReflect(key="lisi",value="22")
public class SimpleBean {
//使用自定义的Annotation,并设置两个属性参数的内容。
@MyAnnotationReflect(key="wangwu",value="25")
public String toString(){
return "Hello Java";
}
}
②、@Target注释的源码定义:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interfaceTarget {
ElementType[] value();
}
注意:在Target注释当中存在一个ElementType[]枚举类型的变量,这个变量的主要作用是限制Annotation的使用位置。
③、ElementType类型限制Annotation的使用范围
序号 | 使用位置(范围) | 解释说明 |
1 | public static final ElementType TYPE | 只能在类、接口、枚举上声明使用 |
2 | public static final ElementType ANNOTATION_TYPE | 只能在注释的声明上使用 |
3 | public static final ElementType CONSTRUCTOR | 只能在构造方法的声明上使用 |
4 | public static final ElementType FIELD | 只能在字段(包括枚举常量)的声明上使用 |
5 | public static final ElementType METHOD | 只能在方法的声明上使用 |
6 | public static final ElementType PARAMETER | 只能在参数的声明上使用 |
7 | public static final ElementType LOCAL_VARIABLE | 只能在局部变量的声明上使用 |
8 | public static final ElementType PACKAGE | 只能在包的声明上使用 |
代码示例1:限定自定义的Annotation只能在类上使用
package com.hy.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) //使用@Target注释明确指定该Annotation只能在类上使用。
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyTargetAnnotation{
public String key() default "JAVA"; //定义变量以及默认值。
public String value() default "Android";
}
@MyTargetAnnotation(key="PHP",value="HTML") //在类上使用自定义的Annotation。
classTargetAnnotationTest{
//在方法上使用自定义的Annotation,会报错,因为Annotation中使用@Target注释明确限定使用的位置是在类上。
@MyTargetAnnotation(key="JAVAEE",value="Hadoop")
public String sayHello(){
return "Hello JAVA & Android";
}
}
注意:
从@Target注释的源码定义当中可以发现其中可以接收一个ElementType枚举类型的数组,也就是说可以指定Annotation同时使用在多个位置上。
代码示例2:指定自定义Annotation同时使用在多个位置上
package com.hy.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD}) //通过在@Target注释中传入一个ElementType类型的数组,可以允许该自定义Annotation同时在类的多个位置上使用。
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyTargetAnnotation{
public String key() default "JAVA";
public String value() default "Android";
}
@MyTargetAnnotation(key="PHP",value="HTML") //在类上使用自定义的Annotation。
classTargetAnnotationTest{
//在方法上使用自定义的Annotation。
@MyTargetAnnotation(key="JAVA EE",value="Hadoop")
public String sayHello(){
return "Hello JAVA & Android";
}
}
2、@Documented注释
①、作用:在生成javadoc文档的时候可以通过@Documented注释将一些文档的说明信息写入进去,编译用户了解类或者方法等的作用。
注:其实任何一个自定义的Annotation实际上都是通过@Documented进行注释的。
②、@Documented注释的使用格式:
[public] @interface Annotation名称{
数据类型变量名();
}
代码示例:对方法进行javadoc注释,并生成.html格式的说明文档
MyDocumentedAnnotation.java
package com.hy.test;
import java.lang.annotation.Documented;
//自定义Annotation,使用@Documented注释。
@Documented
public @interface MyDocumentedAnnotation {
public String key();
public String value();
}
MyDocumentedAnnotationTest.java
package com.hy.test;
@MyDocumentedAnnotation(key="JAVA",value="HTML")
public class MyDocumentedAnnotationTest {
/**
* 此方法在对象输出时调用,返回对象的信息
*/
@MyDocumentedAnnotation(key="JAVA",value="Android")
public String toString(){
return "Hello World";
}
}
然后在DOS命令行下面使用javadoc.exe这个工具的来生成程序说明文档,执行以下语句:
javadoc –dc:\document MyDocumentedAnnotationTest.java
执行成功之后,就会在C:\document 目录下生成javadoc文档,如下图所示:
直接打开document文件夹当中的index.html就可以看到刚刚编写的javadoc说明信息,如下图:
3、Inherited注释
①、作用:@Inherited注释用于标注一个父类的注释是否可以被子类所继承。如果一个Annotation需要被其子类所继承,则在声明时直接使用@Inherited注释即可。
②、@Inherited注释的源码定义:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interfaceInherited {
}
代码示例:验证子类继承父类的Annotation,使用@Inherited注释
//自定义一个使用@Inherited注释的Annotation
MyInheritedAnnotation.java
package com.hy.test;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Inherited //此自定义注释可以被子类继承。
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIneritedAnnotation {
public String name();
}
//定义一个Persons类,再定义一个子类继承Persons类。
Persons.java
package com.hy.test;
//定义父类Persons,使用自定义的Annotation
@MyIneritedAnnotation(name="zhangsan")
class Persons {}
//定义子类继承Persons
class Students extends Persons{}
//通过反射的方式验证父类上使用的Annotation是否被子类继承
MyIneritedDemo.java
package com.hy.test;
importjava.lang.annotation.Annotation;
public class MyInheritedDemo {
public static void main(String[]args) throws Exception {
//获取Class类实例对象。
Class c = Class.forName("com.hy.test.Students");
//获取全部的Annotation。
Annotation[] annotation = c.getAnnotations();
for(Annotation an : annotation){
System.out.println(an); //输出获取到的所有Annotation
}
if(c.isAnnotationPresent(MyIneritedAnnotation.class)){
//获取自定义的Annotation,且此Annotation是从父类继承过来的。
MyIneritedAnnotation mia = (MyIneritedAnnotation) c.getAnnotation(MyIneritedAnnotation.class);
String name = mia.name(); //获取自定义Annotation中的指定变量的内容。
System.out.println("name = "+name);
}
}
}
输出结果:
@com.hy.test.MyIneritedAnnotation(name=zhangsan)
name = zhangsan
结论:如果Annotation没有使用@Inherited注释,则此Annotation是无法被子类所继承的。