Java核心技术基础知识学习之注解


十、Annotation

从 JDK 5 开始,Java 增加对元数据(MetaData)的支持,即 Annotation。Annotation 其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变原有逻辑的基础上,在源文件中嵌入一些补充的信息。
Annotation 提供了一种为程序元素设置元数据的方法,类似于修饰符可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在 Annotation 的name = value 对中。

Annotation 是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来获取注解中的元数据。

如果希望程序中的 Annotation 在运行中起到一定作用,只有通过配套的工具对 Annotation 中的信息进行访问和处理,访问和处理 Annotation 的工具统称为 APT(Annotation Processing Tool)

10.1 基本 Annotation

java.lang 包中5 个基本的 Annotation 如下:

  • @Override
  • @Deprecated
  • @SuppressWarnings
  • @SafeVarargs:Java 7 新增
  • @FunctionalInterface:Java 8 新增

10.1.1 限定重写父类方法:@Override

@Override 就是用来指定方法重载的,它可以强制一个子类必须覆盖父类的方法。其作用就是告诉编译器检查该方法,保证父类要包含一个被该方法重写的方法,否则编译就会出错。@Override 只能修饰方法,不能修饰其他程序元素。

10.1.2 标示已过时:@Deprecated

@Deprecated 用于表示某个程序元素(类、方法等已过时),当程序使用已过时的类时、方法时,编译器会给出警告。

@Deprecated 的作用与文档注释中的 @deprecated 标记的作用基本相同,但用法不同,前者是 JDK 5 才支持的注解,无须放在文档注释语法(/**...*/部分)中,而是直接用于修饰程序中的程序单元,如方法、类、接口等。

10.1.3 抑制编译器警告:@SuppressWarnings

@SuppressWarnings 指示被该 Annotation 修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。@SuppressWarnings 会一直作用于该程序元素的所有子元素。
在通常情况下,如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用 @SuppressWarnings 修饰。

//关闭整个类里的编译器警告
@SuppressWarnings(value = "unchecked")    
public class SuppressWarningsTest {
    public static void main(String[] args) {
        List<String> myList = new ArrayList();
    }
}

当使用 @SuppressWarnings 来关闭编译器警告时,一定要在括号里使用name = value的形式为该 Annotation 的成员变量赋值。

10.1.4 堆污染与 @SafeVarargs

List list = new ArrayList<Integer>();
list.add(20);	//添加元素时引发 unchecked 异常
//下面代码引起“未经检查的转换”异常,编译、运行时完全正常
List<String> ls = list;
//但只要访问 ls 里的元素,就会引起运行时异常
System.out.println(ls.get(0));

Java 将引发这种错误的原因称为堆污染(Heap pollution),当把一个不带泛型的对象赋给带泛型的变量时,往往就会发生堆污染。对于形参个数可变的方法,该形参的类型又是泛型,这将更容易导致堆污染。

public class ErrorUntils {
	public static void faultyMethod(List<String>... listStrArray) {
		//Java 语言不允许创建泛型数组,因此 listArray 只能被当成 List[] 数组
		//此时相当于把 List<String> 赋给 List,已经发生堆污染
		List[] listArray = listStrArray;
		List<Integer> myList = new ArrayList<Integer>();
		myList.add(new Random().nextInt(100));
		//把 listArray 的第一个元素赋为 myList
		listArray[0] = myList;
		String s = listStrArray[0].get(0);
	}
}

使用该方法时

public class ErrorUtilsTest {
	public static void main(String[] args) {
		//编译引发一个 unchecked 警告,运行引发 ClassCastException 异常
		ErrorUtils.faultyMethod(Arrays.asList("Hello!", Arrays.asList("World!")));
	}
}

Java 7 会在定义该方法就发出堆污染警告,如果不希望看到该警告:

  • 使用 @SafeVarargs 修饰引发该警告的方法或构造器;
  • 使用 @SupressWarnings(“unchecked”)修饰;
  • 编译时使用 -Xlint:Varargs 选项;

10.1.5 Java 8 的函数式接口与 @FunctionalInterface

@FunctionalInterface 是用来指定某个接口必须是函数式接口。函数式接口就是为 Lambda 表达式准备的,Java 8 允许使用 Lambda 表达式创建函数式接口的实例,专门增加 @FunctionalInterface。

10.2 JDK 的元 Annotation

JDK 除了在 java.lang 下提供 5 个基本的 Annotation,还在java.lang.annotation 包下提供了 6 个Meta Annotation(元 Annotation),其中有 5 个元 Annotation 都用于修饰其他的 Annotation 定义。其中 @Repeatable 专门用于定义 Java 8 新增的重复注解。

10.2.1 使用 @Retention

@Retention 只能用于修饰 Annotation 定义,用于指定被修饰的 Annotation 可以保留的时间,@Retention 包含一个RetentionPolicy类型的 value 成员变量,所以使用 @Retention 时必须为该 value 成员变量指定值。
value 成员变量的值只能是 3 个:

  • RetentionPolicy.CLASS:编译器将把 Annotation 记录在 class 文件中。当运行 Java 程序时,JVM 不可获取 Annotation 信息,这是默认值;
  • RetentionPolicy.RUNTIME:编译器将把 Annotation 记录在 class 文件中。当运行 Java 程序时,JVM 也可以获取 Annotation 信息,程序可以通过反射获取该 Annotation 信息;
  • RetentionPolicy.SOURCE:Annotation 只保留在源代码中,编译器直接丢弃这种 Annotation;

如果需要通过反射获取注释信息,就需要使用 value 属性值为 RetentionPolicy.RUNTIME 的 @Retention。使用 @Retention 元 Annotation 可采用如下代码为 value 指定值。

//定义下面的 Testable Annotation 保留到运行时间
@Rentation(value = RententionPolicy.RUNTIME)
public @interface Testable{}
//定义下面的 Testable Annotation 将被编译器直接丢弃
@Retention(RetentionPolicy.SOURCE)
public @interface Testable{}

如果使用注解时只需要为 value 成员变量指定值,则使用该注解时可以直接在改注解后的括号里指定 value 成员变量的值,无须使用“value = 变量值”的形式。

10.2.2 @Target

@Target 也只能修饰一个 Annotation 定义,它用于指定被修饰的 Annotation 能用于修饰哪些程序单元。@Target 元 Annotation 也包含一个名为 value 的成员变量,该成员变量的值只能是如下几个:

  • ElementType.ANNOTATION_TYPE:指定该策略的 Annotation 只能修饰 Annotation;
  • ElementType.CONSTRUCTOR:指定该策略的 Annotation 只能修饰构造器;
  • ElementType.FIELD:指定该策略的 Annotation 只能修饰成员变量;
  • ElementType.LOCAL_VARIABLE:指定该策略的 Annotation 只能修饰局部变量;
  • ElementType.METHOD:指定该策略的 Annotation 只能修饰方法定义;
  • ElementType.PACKAGE:指定该策略的 Annotation 只能修饰包定义;
  • ElementType.PARAMETER:指定该策略的 Annotation 只能修饰参数;
  • ElementType.TYPE:指定该策略的 Annotation 只能修饰类、接口(包括注解类型)或枚举定义;

与使用 @Retention 类似的是,使用 @Target 也可以直接在括号里指定 value 值,而无需使用name = value的形式。

//指定 @ActionListenerFor 只能修饰成员变量
@target(ElementType.FIELD)
public @interface ActionListenerFor{}

10.2.3 @Documented

@Documented 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。如果定义 Annotation 类时使用了 @Documented 修饰,则所有使用该 Annotation 修饰的成员元素的 API 文档中将包含该 Annotation 说明。

10.2.4 @inherited

@inherited 指定被它修饰的 Annotation 将具有继承性,如果某个类使用 @inherited 修饰,则该类的子类将自动使用 @inherited 修饰。

10.3 自定义 Annotation

10.3.1 定义 Annotation

定义新的 Annotation 类型使用 @interface 关键字(在原有的 interface 关键字前加@符号)定义一个新的 Annotation 类型与定义一个接口相似。

//定义一个简单的 Annotation 类型
public @interface Test{}

Annotation 的语法类似于 public、final 修饰符,通常用于修饰程序中的类、变量、接口等定义。Annotation 的成员变量还可以在 Annotation 定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。

public @interface MyTag
{
	//定义两个成员变量的 Annotation
	//Annotation 中的成员变量以方法的形式来定义
	String name();
	int age();
}
public class Test
{
	//使用带成员变量的 Annotation 时,需要为成员变量赋值
	@MyTag(name = "xx", age = 18)
	public void info()
	{
		...
	}
}

定义 Annotation 的成员变量还可以用 default 关键字指定默认初始值,使用时不需要赋值。

public @interface MyTag
{
	//定义两个成员变量的 Annotation 使用 default
	String name() default "wang";
	int age() default 32;
}

根据 Annotation 是否可以包含成员变量,分为两类:

  • 标记 Annotation:没有定义成员变量的 Annotation 类型被称为标记,仅利用自身的存在与否提供信息,如 @override;
  • 元数据 Annotation:包含成员变量的 Annotation,可以接受更多的元数据;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值