Java学习总结——Annotation

贰拾壹——给编译器看的注解

Annotation( 注解 )功能建立在反射机制之上,通过这个功能可对程序进行注释操作。

一、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( 应用程序接口 )的方式提供对其信息的访问。

二、系统内建的 Annotation

在 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 用于有选择地关闭编译器对类、方法、成员变量、变量初始化的警告。

三、自定义 Annotation

上面三种是 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 和 RetentionPolicy

在注解中,可以使用 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 虚拟机中。

五、反射与 Annotation

注解若想发挥更大作用,还需借反射机制之力。通过反射,可以取得在一个方法上声明的注解的全部内容。

在 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

前面的 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。

你可能感兴趣的:(Java)