Java —— Annotation(注解)
2016-01-28 16:47
429 查看
Annotation概述
Annotation即注解,也被翻译为注释,是JDK 5 Java对元数据(MetaData)的支持。Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变逻辑的情况下,在源文件嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行不熟。Annotation提供了一种为程序元素设置数据的方法,从某些方面来看,Annotation就像修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的“name=value”对中。
Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据。值得指出的是,Annotation不影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称APT(Annotation Processing Tool)。
5个基本Annotation
在系统学习新的Annotation语法之前,下来看一下Java提供的5个基本Annotation的用法。5个基本的Annotation:
1.@Override
2.@Deprecated
3.@SuppressWarnings
4.@SafeVarargs
5.@FunctionalInterface
上面5个基本Annotation中的@SafeVarargs是Java 7新增的、@FunctionalInterface是Java 8新增的。这5个基本的Annotation都定义在java.lang包下,读者可以通过查阅他们的API文档来了解关于它们的更多细节。
下面我们来逐个解释一下这个5个基本的Annotation。
限定重写父类方法:@Override
@Override就是用来指定一个方法是重写父类中的方法的。例如:[code] @Override public String toString() { return super.toString(); }
上面是重写Object的toString()方法,我们用@Override来标记这个方法是重写父类Object中的toString()方法。如果不类不存在toString()方法,在编译时就会报错。
@Override主要是帮助程序员避免一些低级错误。例如父类中有个一个execute()的方法,但是我们在重写此方法的时候把方法名字写错了。这样我们在调用这个写错了的方法时不会影响程序的运行,但是得到的结果可能与我们的预期不同。这样的错误排查起来也非常费时费力,有了@Override可一定程度的解决这一问题的发生。
标识已过时:@Deprecated
@Deprecated用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。例如:[code]public class Test { public static void main(String[] args) { // 下面使用info()方法是将会被编译器警告 new Apple().info(); } } class Apple { // 定义info方法已过时 @Deprecated public void info() { System.out.println("Apple的info方法"); } }
抑制编译器警告:@SuppressWarnings
@SuppressWarnings指示被该Annotation修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译警告。@SuppressWarnings会一直作用于该程序元素的所有自元素,例如,使用@SuppressWarnings修饰某个类取消显示某个编译器警告,同时you修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。[code]@SuppressWarnings(value="unchecked") public class Test { public static void main(String[] args) { List<String> myList = new ArrayList(); } }
如上代码所示,new ArrayList();没有带有泛型,所以编译器会有警告,如果想要消除次警告有两种办法,一个是增加泛型,另一个则是使用@SuppressWarnings来标记Test或者标记main方法,也可以直接标记new ArrayList()这一条语句。这样编译器警告就会消失。
Java 7的“堆污染”警告与@SafeVarargs
[code] public static void main(String[] args) { List list = new ArrayList<Integer>(); list.add(20); // 添加元素引发unchecked异常 // 下面代码引起“未经检查的转换”的警告,编译、运行时完全正常 List<String> ls = list; // 但只要访问ls里的元素,如下面代码就会引起运行时异常 System.out.println(ls.get(0)); }
Java把引发这种错误的原因称为“堆污染”(Heap pollution),当把一个不带泛型的对象赋给一个带泛型的变量时,往往就会发生这种“堆污染”,如上面的List< String > ls = list;这句代码。
三种“抑制”这个警告的方法:
1.使用@SafeVarargs修饰引发该警告的方法或构造器
2.使用@SuppressWarnings(“unchecked”)修饰。
3.编译时使用-Xlint:varargs选项
很明显,第三种方式一般比较少用,通常可以选择第一种或第二种方式,尤其是使用@SafeVarargs修饰引发该警告的方法构造器,它是Java 7专门为抑制“堆污染”警告提供的。
Java 8的函数式接口与@FunctionalInterface
@FunctionalInterface在Lambda博文中已经讲过了。它就是用来指定某个接口必须是函数式接口(一个接口中只能有一个抽象方法),否则编译时就会出错,这是为了防止程序员犯一些第几错误。JDK的元Annotation
JDK除了在java.lang下提供了5个基本的Annotation之外,还在java.lang.annotation包下提供了6个Meta Annotation(元Annotation),其中有5个元Annotation都用于修饰其他的Annotation定义。其中@Repeatable专门用于定义Java 8新增的重复注解。接下来我们逐个解释。@Retention
@Retention只能用于修饰Annotation定义,用于指定被修饰的Annotation可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值。value成员变量的值只能是如下三个:
1.RetentionPolicy.CLASS:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM不可获取Annotation信息。这是默认值。
2.RetentionPolicy.RUNTIME:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM也可获取Annotation信息。程序可以通过反射获取该Annotation信息。
3.RetentionPolicy.SOURCE:Annotation只能留在源码中,编译器直接丢弃这种Annotation。
如果需要通过反射获取注解信息,就需要使用value属性值为RetentionPolicy.RUNTIME的@Retention。使用@Retention元Annotation可采用如下代码为value指定值。
[code]// 1.定义下面的Testable Annotation保留运行时 @Retention(value = RetentionPolicy.RUNTIME) public @interface Testable{} // 2.定义下面的Testable Annotation将被编译器直接丢弃 @Retention(RetentionPolicy.SOURCE) public @interface Testable{}
如第二种方式所示,当Annotation的变量名为value时,程序中可以直接在Annotation后的括号里指定该成员变量的只,无须使用name=value的形式。
@Target
Target也只能修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰那些程序单元。@Target元Annotation也包含一个名为value的成员变量,该成员变量的值只能是如下几个。1.ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation。
2.ElementType.CONSTRUCTOR:指定该策略的Annotation只能修饰构造器。
3.ElementType.FIELD:指定该策略的Annotation只能修饰成员变量。
4.ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量。
5.ElementType.METHOD:指定该策略的Annotation只能修饰方法定义。
6.ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义。
7.ElementType.PARAMETER:指定该策略的Annotation只能修饰参数。
8.ElementType.TYPE:指定该策略的Annotation只能修饰类、接口(包括注解类型)或枚举定义。
与使用@Retention类似的是,使用@Target也可以直接在括号里指定value值,而无须使用name=value的形式。如下代码指定@ActionListenerFor Annotation只能修饰成员变量。
[code]@Target(ElementType.FIELD) public @interface ActionListenerFor{}
@Documented
@Documented用于指定被该元Annotation修饰的Annotation类江北javadoc工具提取成 文档,如果定义Annotation类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。@Inherited
@Inherited元Annotation指定被它修饰的Annotation将具有继承性——如果某个类使用了@Xxx注解(定义该Annotation时使用了@Inherited修饰)修饰,则其子类将自动被@Xxx修饰。自定义Annotation
我们通过一个完整的例子来演示一下自定义Annotation。代码如下:[code]@Testable(name="hello",age=20) public class Test { public static void main(String[] args) { } } // 定义下面的Testable Annotation保留运行时 @Retention(RetentionPolicy.RUNTIME) // 可以修饰类、接口或枚举 @Target(ElementType.TYPE) // 可以被javadoc工具提取成文档 @Documented // 具有继承性 @Inherited @interface Testable{ // 定义成员变量name,默认值为Lyong String name() default "Lyong"; // 定义成员变量age,默认值为18 int age() default 18; }
上面的例子已经有了详细的注释,我们在来说一下自定义Annotation时一些规则或者注意点。
1.Annotation上的各种修饰之前已经讲解过,我们自定义Annotation时这些是可选的,根据我们的需求去添加不同的Annotation来修饰。
2.声明Annotation与生命接口类似,只不过是将interface关键字之前加了一个@符号。
3.成员变量的声明方式使用类似声明方法一样。
4.成员变量可以有默认值,如果有默认值则使用default关键字后面加默认值,当使用次Annotation时如果为成员变量指定值则默认值无效,否则使用默认值。
根据Annotation是否可以包含成员变量,可以把Annotation分为如下两类。
1.标记Annotation:没有定义成员变量的Annotation类型被称为标记。这种Annotation仅利用自身的存在与否来提供信息,如前面介绍的@Override、@Test等Annotation。
2.元数据Annotation:包含成员变量的Annotation,因为他们可以接受更多的元数据,所以也被称为元数据Annotation。
提取Annotation信息
使用Annotation修饰了类、方法、成员变量等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的工具来提取并处理Annotation信息。Java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。Java 5在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。接口主要如下几个实现类。
1.Class:类定义。
2.Constructor:构造器定义。
3.Field:类的成员变量定义。
4.Method:类的方法定义。
5.Package:类的包定义。
java.lang.reflect包下主要包含一些实现反射功能的工具类。从Java 5开始,java.lang.reflect包所提供的反射API增加了读取运行时Annotation的能力。只有当定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)修饰,该Annotation才会在运行时可见,JVM才会在装在*.class文件时读取保存在class文件中Annotation。
AnnotatedElement接口是所有程序元素(如Class、Method、Constructor等)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor等)之后,程序就可以调用该对象的如下几个方法来访问Annotation信息。
1.< A extends Annotation > A getAnnotation(Class< A > annotationClass):返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null。
2.< A extends Annotation > A getDeclaredAnnotation(Class< A > annotationClass):这是Java 8新增的方法,该方法尝试获取直接修饰该程序元素、指定类型的Annotation。如果该类型的注解不存在,则返回null。
3.Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
4.Annotation[] getDeclaredAnnotations():返回直接修饰该程序元素的所有Annotation。
5.boolean isAnnotationPresent(Class< ? extends Annotation > annotationClass):判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。
6.< Aextends Annotation > A[] getAnnotationsByType(Class< A > annotationClass):该方法的功能与前面介绍的getAnnotation()方法基本相似。但由于Java 8增加了重复注解功能,因此需要使用该方法获取修饰该程序元素、指定类型的多个Annotation。
7.< Aextends Annotation > A[] getDeclaredAnnotationsByType(Class< A > annotationClass):该方法的功能与前面介绍的getDeclaredAnnotations()方法基本相似。但由于Java 8增加了重复注解功能,因此需要使用该方法获取直接修饰该程序元素、指定类型的多个Annotation。
接下来我们看一个简单的提取注解信息的例子:
[code]// 使用自定义TestAnnotation注解,name赋值为“arry”,index赋值为1 @TestAnnotation(name = "array", index = 1) public class Test { public static void main(String[] args) { // 声明一个class对象 Class<?> cls; try { // 获取当前类的Class对象 cls = Class.forName("Test"); // 如果当前cls包含TestAnnotation注解将开始提取注解信息 if(cls.isAnnotationPresent(TestAnnotation.class)) { // 获取当前cls上所有的注解信息 Annotation[] anns = cls.getAnnotations(); // 遍历所有获取到的注解信息 for(Annotation ann : anns) { // 如果有注解信息是TestAnnotation类型的将打印出TestAnnotation的name和index值 if(ann instanceof TestAnnotation) { // 输出注解的信息 System.out.println("name = " + ((TestAnnotation)ann).name() + "---index = " + ((TestAnnotation)ann).index()); } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } // 运行时可以获取Annotation信息 @Retention(RetentionPolicy.RUNTIME) // 可以修饰类、接口、枚举 @Target(ElementType.TYPE) @interface TestAnnotation { // 字符串成员name,默认值为“Lyong” String name() default "Lyong"; // int类型成员index,默认值为0 int index() default 0; }
例子有详细的注释信息,大家可以参考一下,联系一下其他类型的注解信息。
Java 8 新增的重复注解
在Java 8以前,同一个程序元素前最多只能使用一个相同类型的Annotation;如果需要在同一个元素前使用多个相同类型的Annotation,则必须使用Annotation“容器”。例如如下代码形式:[code]@Results({@Result(name="failure",location="failed.jsp"), @Result(name="success",location="succ.jsp")}) public Action FooAction{...}
上面代码中使用了两个@Result注解,但由于传统Java语法不允许多次使用@Result修饰同一个类,因此程序必须使用@Results注解作为两个@Result的容器——实质是,@Result注解只包含了一个名字为value、类型为Result[]的成员变量,程序指定的多个@Result作为@Results的value属性(数组类型)的数组元素。
从Java 8开始,上面语法可以得到简化:Java 8允许使用多个相同类型的Annotation修饰同一个类,因此上面代码可能(之所以说可能,是因为重复注解还需要对原来的注解进行改造)可简化为如下形式:
[code]@Result(name="failure",location="failed.jsp") @Result(name="success",location="succ.jsp") public Action FooAction{...}
我们来一步一步的演示一下重复注解的创建过程:
1.首先开发重复注解需要使用@Repeatable修饰
[code]@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Repeatable(Testables.class) @interface Testable{ String name() default "Lyong"; int age() default 18; }
上面我们创建了一个Testable Annotation,并声明为可在运行时获取信息,Testable可以修饰类、接口、枚举、并使用了@Repeatable来修饰此Annotation,表示可以使用多个Testable修饰同一个类。接下来我们看看“容器”的代码。
[code]@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface Testables{ Testable[] value(); }
“容器”的不同只是成员变量是我们之前定义的Testable,也就是我们想在一个类上使用的多个Annotation,还有一点需要注意的是“容器”的@Retention的“声明周期”必须要比Testable的大。
接下来我们就可以使用重复注解了。
[code]@Testable(name="hello",age=20) @Testable(name="world",age=21) public class Test { public static void main(String[] args) { } }
注意:重复注解只是一种简化写法,这种简化写法是一种假象:多个重复注解其实会被作为“容器”注解的value成员变量的数据元素。例如上面的重复的@Testable注解其实会被作为@Testables注解的value成员变量的数据元素处理。
Java 8新增的Type Annotation
Java 8为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值,这样就允许定义枚举时使用@Target(ElementType.TYPE_USE)修饰,这种注解被称为TypeAnnotation(类型注解),TypeAnnotation可用在任何用到类型的地方。在Java 8以前,只能在定义各种程序元素(定义类、定义接口、定义方法、定义成员变量等)时使用注解。Java 8开始,TypeAnnotation可以在任何用到类型的地方使用。比如,允许在如下位置使用TypeAnnotation。
1.创建对象(用new关键字创建)。
2.类型转换。
3.使用implements实现接口。
4.使用throws声明抛出异常。
上面这些清醒都会用到类型,因此都可以使用类型注解修饰。
[code]// 定义类时使用Type Annotation @NotNull public class Test implements @NotNull /* implements时使用Type Annotation */Serializable { // 方法参数中使用Type Annotation public static void main(@NotNull String[] args) throws @NotNull FileNotFoundException // throws时使用Type Annotation { Object obj = "helloworld"; // 强制类型转换时使用Type Annotation String str = (@NotNull String)obj; // 创建对象时使用Type Annotation Object win = new @NotNull JFrame("Title"); } // 泛型中使用Type Annotation public void foo(List<@NotNull String> info){ } } // 定义一个简单的Type Annotation,不带任何成员变量 @Target(ElementType.TYPE_USE) @interface NotNull { }
上面的代码都是可正常使用Type Annotation的例子,从这个示例可以看到,Java程序到处“写满”了Type Annotation,这种“无处不在”的Type Annotation可以让编译器执行更严格的代码检查,从而提高程序的健壮性。
需要指出的是,上面程序虽然大量使用了@NotNull注解,但这些注解暂时不会起任何作用——因为没有为这些注解提供处理工具。而且Java 8本身没有提供对Type Annotation执行检查的框架,因此如果需要让这些Type Annotation发挥作用,开发者需要自己实现Type Annotation检查框架。
幸运的是,Java 8提供了Type Annotation之后,第三方组织在发布他们的框架时,可能会随着框架一起发布Type Annotation检查工具,这样普通开发者即可直接使用第三方框架提供TYpe Annotation,从而让编译器执行更严格的检查,保证代码更加健壮。
相关文章推荐
- java如何通过socket实现服务端与客户端交互
- JAVA NIO之浅谈内存映射文件原理与DirectMemory
- 几种任务调度的 Java 实现方法与比较
- 无法自动生成R.java中的内部类代码
- JAVA获取json中的全部键值对
- 初识java 多线程
- JAVA的23中涉及模式详解
- Java并发编程笔记 死锁的产生与范例分析
- Spring
- java.io.IOException: Prepare failed.: status=0x1异常解决方法
- 实际项目中关于java常量宏替换的实际问题
- 打印九九乘法表
- springMvc integrated with springSecurity 常见问题
- 【spring mvc (三)】spring 容器
- Eclipse用户使用IntelliJ IDEA的常见问答
- eclipse项目转android studio的问题
- spring xml读取Properties文件中的加密字段
- java运算符
- Java设计模式(六)----适配器模式
- 开发 OpenAM Java 客户端