Annotation( 注解 )功能建立在反射机制之上,通过这个功能可对程序进行注释操作。
Annotation( 中文翻译为 “ 注解 ”,或 “ 注释 ” )实际上表示的是一种注释的语法,这种注释和代码的注释是不一样的,代码的注释( 如单行注释用双斜杠 “ // ”,多行注释用 “ /*...*/ 等 ” )是给程序员看的,其主要目的是增加代码的可读性,便于代码的后期维护。而 Annotation,主要是服务于编译器,属于一种配置信息。早期的 Java 程序提倡程序与配置文件相分离,代码是代码,注释是注释。但后来的实践发现,配置文件过多,以至于配置信息修改起来非常困难,所以将配置信息直接写入到程序之中的理念又重新得到应用。
而若想要在 Java 中完成这样的功能,就需要使用 Annotation。在流行的 SSH 框架( 即:Spring——开源的 Java/Java EE 全功能栈的情况及的应用程序框架,Struts2——用于开发 Java EE 网络应用程序的开源 Web 应用框架,Hibernate——Java 语言下的对象关系映射解决方案,它对 JDBC 进行了轻量级的对象封装,是一种数据库持久层框架 )等中大量使用了这个技术。
在本质上,Annotation 提供了一种与程序元素关联任何信息或者任何元数据( metadate )的方式。Annotation 可以像修饰符一样被使用,可以应用于任何程序元素( 如包、类型、构造方法、方法、成员变量、参数、本地变量 )的声明中。这些信息被存储在 Annotation 的 “ name = value ” 结构对中。事实上,Annotation 类型是一种接口,能够通过 Java 反射 API( 应用程序接口 )的方式提供对其信息的访问。
在 JDK 1.5 之后的系统中,内建了 3 个 Annotation:@Override、@Deprecated、@SuppressWarnings。
1.@Override
如果要进行方法的覆写,那么要求是:方法名称、参数的类型及个数完全相同,而在开发之中有可能会由于手误等原因导致方法不能被正确地覆写。
举例:
//由于手误,导致覆写错误
class Message
{
public String tostring() //原本打算覆写toString()
{
return "Hello";
}
}
public class OverrideError
{
public static void main(String[] args)
{
System.out.println(new Message());
}
}
原本打算覆写 toString() 方法,却由于手误导致覆写 “ 错误 ”——tostring(),其中的 S 字符被错误小写,而 Java 是区分大小写的,这时不会产生编译错误,因为 JDK 会认为 tostring() 是一个新的方法,可是从实际需求上来讲,这个方法应该是被覆写的。因此为了保证这种错误在程序编译的时候就可以被发现,可以在方法覆写时增加上 “ @Override ” 注解。@Override 用在方法之上,就是用来告诉编译器,这个方法是用来覆写来自父类的同名方法的,如果父类没有这个所谓的 “ 同名 ” 方法,就会发出警告信息。
//使用@Override Annotation
class Message
{
@Override
public String tostring() //原本打算覆写toString()
{
return "Hello";
}
}
public class OverrideError
{
public static void main(String[] args)
{
System.out.println(new Message());
}
}
这样就可以在编译时发生编译错误( 使用 Eclipse 会有错误提示 ),提示 :" 类型为 Message 的方法 tostring() 必须覆盖或实现超类型方法 "。这样添加了注解,就可以及时在编译时就发现错误,并提示用户早点改正错误,以防日后维护困难。
如果将注解提示的错误纠正过来,将 s 字符改为 S 字符,就完成了真正的覆写。
2.@Deprecated
在 API 中经常会看到某个方法下面有 “ Deprecated ” 这个单词,表示这个方法已经过时,不建议使用,如 Sring 类的构造方法中就有这样一个方法。
String(byte[] ascii,int hibyte)
这个过时的方法并不能将多个字节准确地转换为字符,自从 JDK 1.1 起,完成该转换的首选方法是通过 String 构造方法,该方法接受一个字符集名称或使用平台的默认字符集。
标识某个方法过时的功能可以使用 @Deprecated 的注解来实现。
举例:
//使用@Deprecated Annotation的效果
public class DeprecatedAnnotation{
public static void main(String[] args){
Info info = new Info();
//使用getInfo方法并不会产生编译错误,只是不建议使用这个方法
System.out.println(info.getInfo());
}
}
class Info{
@Deprecated
public String getInfo(){
return "Hello";
}
}
示例中使用了 @Deprecated 注解,就是用来建议别人不要使用某些旧的方法( 或 API ),这时编译的时候就会产生警告信息,它也可以设定在程序中所有的元素上,来表明某个元素是过时的。
过时的方法还保留至今,这主要是为了保证对过去开发软件的兼容性。
3.@SuppressWarnings
有没有遇到过这样的情况,明明这个问题是你知道的,Eclipse 还是不停地提示你。而这个时候如果不想让其显示的话,就可以使用 @SuppressWarnings 的注解压制警告的信息。
举例:
//使用@SuppressWarnings Annotation的效果
public class SuppressWarningsAnnotation
{
public static void main(String[] args){
//压制"未使用"警告信息
@SuppressWarnings("unused")
int i;
System.out.println("Hello");
}
}
示例中使用了 @SuppressWarnings("unused"),这样,局部变量 i 虽然声明了没有使用,也不会弹出警告信息。注解 @SuppressWarnings 用于有选择地关闭编译器对类、方法、成员变量、变量初始化的警告。
上面三种是 Java 内置的注解,只要通过固定的格式调用即可。下面是扩展类型的注解——自定义 Annotation。
自定义 Annotation 的语法如下:
[public] @interface Annotation 类名称
{
{数据类型 变量名称();}
}
要自定义注解,需要使用 @interface 的方式进行定义,但是从上面的格式可以发现,在定义注解时也可以定义各种变量,但是变量定义之后必须使用括号 ( ) 。
使用 @interface 就相当于继承了 Annotation 接口。在程序中只要使用了 @interface 声明 Annotation,那么此 Annotation 实际上就相当于继承了 java.lang.annotation.Annotation 接口。
举例:
//自定义Annotation的效果
@interface MyAnnotation
{
//do nothing
}
public class Test
{
//使用自定义的MyAnnotation Annotation
@MyAnnotation
public static void main(String[] args)
{
System.out.println("Hello");
}
}
示例中只定义了一个 MyAnnotation Annotation,其中没有定义任何的变量。当然也可以在自定义的 Annotation 中定义变量。如下:
//在自定义Annotation中定义变量
@interface MyAnnotationVar
{
public String key();
public String value();
}
public class TestAnnoVar
{
//使用自定义的MyAnnotation Annotation
@MyAnnotationVar(key="var1",value="test")
public static void main(String[] args)
{
System.out.println("Hello");
}
}
如果在自定义 Annotation 中声明变量,且没有设置默认值,则在使用该 Annotation 时必须为变量赋值。如下所示:
//在自定义Annotation中定义变量
@interface VarDefault
{
public String key() default"var1";
public String value() default "test";
}
public class TestVarDefault
{
@VarDefault()
public static void main(String[] args)
{
System.out.println("Hello");
}
}
在上例代码中定义了一个 Annotation,其中声明了 key 和 value 两个变量,并分别设置了默认值。在使用该自定义的 Annotation 时并没有为变量赋值,则编译器就会自动使用默认值为变量赋值。
Annotation 中变量的内容可以通过枚举指定范围,如下所示:
//在自定义Annotation中使用枚举
@interface AnnotationEnum
{
public Color color();
}
enum Color
{
RED,GREEN,BLUE;
}
public class TestAnnotationEnum{
@AnnotationEnum(color=Color.BLUE)
public static void main(String[] args)
{
System.out.println("Hello");
}
}
运行结果为:Hello。
在注解中,可以使用 Retention( 中文含义为 “ 保留 ” )来定义一个 Annotation 的保存范围。它的定义如下:
@Documented
@Retention(value = RUNTIME)
@Target(value = ANNOTATION_TYPE)
public @interface Retention{
RetentionPolicy value();
}
其中,@Documented 有注解的作用,将自定义注解设置为文档说明信息。
RetentionPolicy 是一个枚举类型,用于指定 Annotation 的保存范围。RetentionPolicy 包含 3 个取值范围:
(1)SOURCE:此 Annotation 类型的信息只会记录在源文件中,编译时将被编译器丢弃,及此 Annotation 信息不会保存在编译好的类文件中。
(2)CLASS:编译器将把注释记录在类文件中,但不会被加载到 JVM 中。如果一个 Annotation 声明时没有指定范围,则系统默认值是 CLASS。
(3)RUNTIME:此 Annotation 类型的信息将会保留在源文件、类文件中,在执行时也会加载到 Java 虚拟机( JVM )中,因此可以反射性地读取。
Java 内建的 3 个 Annotation 的保存范围分别为:
(1)Override 定义采用的是 @Retention(value = SOURCE),注解信息只能在源文件中出现。
(2)Deprecated 定义采用的是 @Retention(value = RUNTIME),注解信息在执行时出现。
(3)SuppressWarnings 定义采用的是 @Retention(value = SOURCE),注解信息只能在原文件中出现。
举例:
//自定义Annotation的保存范围
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//定义该自定义Annotation的保存范围是RUNTIME
@Retention(value = RetentionPolicy.RUNTIME)
public @interface RetentionAnnotation
{
public String value();
}
在上例中,Retention 的 value 值被设置为 RUNTIME( 运行时 ),RetentionAnnotation 的注解信息会保留在源文件、类文件及 Java 虚拟机中。
注解若想发挥更大作用,还需借反射机制之力。通过反射,可以取得在一个方法上声明的注解的全部内容。
在 Filed、Method、Constructor 的父类上定义了以下与 Annotation 反射操作相关的方法。
(1)取得全部的 Annotation;
(2)判断操作的是否是指定的 Annotation。
1.取得全部的 Annotation
举例:
//使用反射取得全部的Annotation
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
class Info
{
@Override
@Deprecated
@SuppressWarnings(value = "This is a warning!")
public String toString(){
return "Hello";
}
}
public class GetAnnotations
{
public static void main(String[] args) throws Exception
{
Class> cls = Class.forName("Info");
Method toStringMethod = cls.getMethod("toStirng");
//取得全部的Annotation
Annotation ans[] = toStringMethod.getAnnotations();
for (int i = 0; i < ans.length; i++) {
System.out.println(ans[i]);
}
}
}
运行结果为:@java.lang.Deprecated()。
在上例中,给 toString() 方法加了 3 个内建 Annotation。在 for 循环中获得了 toSting 方法按上的所有 Annotation。但是,3 个内建的 Annotation 中只有 @Deprecated 是 RUNTIME 类型。所以只输出了 Deprecated。也就是说,只有定义采用的是 @Retention(value = RUNTIME) 的 Annotation 才能在程序运行时被反射机制取得。
2.取得指定的 Annotation
举例:
//使用反射取得全部的Annotation
import java.lang.annotation.*;
import java.lang.reflect.Method;
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation
{
public String key();
public String value();
}
class Info
{
@Override
@Deprecated
@SuppressWarnings(value = "")
@MyAnnotation(key="one",value="test")
public String toString(){
return "Hello";
}
}
public class GetAnnotation
{
public static void main(String[] args) throws Exception
{
Class> cls = Class.forName("Info");
Method toStringMethod = cls.getMethod("toString");
//判断该方法上是否有指定类型的Annotation存在
if(toStringMethod.isAnnotationPresent(MyAnnotation.class))
{
MyAnnotation my = null; //声明Annotation的对象
my = toStringMethod.getAnnotation(MyAnnotation.class);
String key = my.key();
String value = my.value();
System.out.println(key + "→" + value);
}
}
}
在上例代码中,定义了一个自定义的注解 MyAnnotation。然后给 toString() 方法定义了 4 个注解:Override( 覆写 )、Deprecated( 过时 )、SuppressWarnings( 压制警告 )、MyAnnotation( 自定义注解 )。最后进行判断是否 toString 方法上有指定的注解( 也就是 MyAnnotation ),然后获得该注解,并取得之中的变量值,之后输出。
前面的 Annotation 在 java.lang.annotation 包下。然而在该包下还有以下的 Annotation:Target、Documented、Inherited,以下将分别介绍。
1.Target
如果一个 Annotation 没有明确地指明使用的位置,则可以在任意的位置使用。那么如果要让一个自定义的 Annotation 只能在指定的位置上使用。例如,只能在类上或方法上使用。这时可以使用 @Target Annotation。
@Target Annotation 明确地指出了一个 Annotation 的使用位置。在 @Target Annotation 中存在一个 ElementType[] 枚举类型的变量,这个变量主要用于指定 Annotation 的使用限制。ElementType 类定义了下面 8 个取值。
(1)ANNOTATION_TYPE:只能用在注释上;
(2)CONSIRUCTOR:只能用在构造方法的声明上;
(3)FOELD:只能用在字段的声明( 包括枚举常量 )上;
(4)LOCAL_VARIABLE:只能用在局部变量的声明上;
(5)METHOD:只能用在方法的声明上;
(6)PACKAGE:只能用在包的声明上;
(7)PARAMEIER:只能用在参数的声明上;
(8)TYPE:只能用在类、接口、枚举类型上。
举例:
//限制自定义Annotation的使用范围
import java.lang.annotation.*;
//当value的值只有一个时可以省略花括号"{"和"}"
@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyAnnotation
{
public String key();
public String value();
}
上例代码中将 @Target Annotation 的 value 值设为 METHOD,因此 MyAnnotation 只能在方法声明上使用,用在其他地方都会产生编译错误。
如果要为 value 设置多个值,例如,给自定义 Annotation 可设置在类以及方法上使用,可以使用下面的方法。
value = {ElementType.TYPE,ElementType.METHOD}
也就是使用数组的初始化方式。而如果 value 的值只取一个,可省略花括号 “ { } ”。
2.Documented 注释
查看在取得指定的 Annotation 的示例代码中生成的 Javadoc 文档( 为具有 default 权限的成员创建 Javadoc ),打开Info 类的文档可以看到 toString 方法的详细资料。
可以发现 @Deprecated Annotation 出现在详细资料上,而 @Override、@SuppressWarnings 和 MyAnnotation 却都没有,这是为什么呢?
其实这就是 @Documented 注解的作用,它可将自定义的注解设置为文档说明信息,因此在生成 Javadoc 时就会将该注解加入文档。下例代码是改过的 MyAnnotation,会使得 MyAnnotation 注解也可以出现在 Javadoc 文档中。
import java.lang.annotation.*;
//使用@Documented Annotation
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation
{
public String key();
public String value();
}
class Info
{
@Override
@Deprecated
@SuppressWarnings(value = "")
@MyAnnotation(key="one",value="test")
public String toString(){
return "Hello";
}
}
再重新生成 Javadoc 文档,会发现 MyAnnotation 已经在文档中了。
3.Inherited
@Inherited 用于标注一个父类的 Annotation 是否可以被子类继承,如果一个 Annotation 需要被其子类所继承,则在声明时直接使用 @Inherited 注释即可。如果没有写上此注释,则此 Annotation 无法被继承。
举例:
//使用@Inherited Annotation
import java.lang.annotation.*;
@Inherited
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation
{
}
@InheritedAnnotation
class Person
{
}
class Student extends Person
{
}
虽然 Student 类上并没有直接使用 InheritedAnnotation Annotation,但是却从 Person 类上继承了下来,所以可以通过反射机制取出该 Annotation。
1.Annotation 是 JDK 1.5 之后新增的功能,主要是使用注释的形式进行程序的开发,通常配合反射、枚举等机制使用。
2.一个 Annotation 要想配合反射机制就必须设置 @Retention(value = RetentionPolicy.RUNTIME)。
3.@Target Annotation 可以指定一个 Annotation 的使用范围。
4.如果一个 Annotation 希望被使用的子类所继承,则要使用 @Inherited Annotation。