简介
注解也被称为元数据,它为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。
注解一定程度上是把元数据和源代码文件结合在一起,而不是保存在外部文档中这一大的趋势之下所催生的。
在Java中,有一些内置的注解,比如:
- @Override:表示当前的方法顶柜将覆盖超类中的方法,如果拼写错误或者覆盖不符合标准,编译器就会发出错误提示。
- @Deprecated:表示被修饰的元素已经被弃用,不建议程序员使用。
- @SuppressWarnings:关闭不当的编译器警告信息
除了这三个还有一些我们后面会提到
基本语法
注解定义
下面是一个注解的定义。可以看到,注解看起来很像接口的定义,事实上,与其他任何Java接口一样,注解也将会编译成class文件。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo1 {
}
除了@符号以外,它很像一个空接口。
在定义注解时,会需要一些元注解,如@Target和@Retention。前者用来定义注解将应用于何处(类、方法还是域),后者定义该注解的可用级别(源代码,编译时,运行时)
在注解中,通常都会包含一些元素以表示某些之。当分析处理注解时,程序或工具可以利用这些值。注解中的元素看起来就像是接口的方法,唯一的区别就是我们可以指定其默认值。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo1 {
public int id();
public String description() default "no description";
}
注意,id和description类似方法定义。如果在使用该注解时没有给出description值,则该注解的处理器会使用此元素的默认值。
下面是此注解的使用示例
class demo{
@AnnotationDemo1(id=1)
public void f1(){
}
@AnnotationDemo1(id=2,description = "f2")
public void f2(){
}
}
元注解
Java目前只内置了三种标准注解(一开始提到的三个注解)以及四种元注解。元注解专职负责注解其他注解:
注解 | 说明 | 参数 |
---|---|---|
@Target | 表示该注解可以用于什么地方 | CONSTRUCUOR:构造器 FIELD:域 LOCAL_VARIABLE:局部变量 METHOD:方法声明 PACKAGE:包声明 PARAMETER:参数说明 TYPE:类、接口(包括注解)或enum |
@Rentention | 表示需要在什么级别保存该注解信息 | SOURCE:注解将被编译器丢弃 CLASS:注解在class文件中可用。会被jvm丢弃 RUNTIME:JVM在运行期间保留注解,因此可以通过反射机制读取注解信息 |
@Documented | 将此注解包含在javadoc中 | |
@Inherited | 允许子类继承父类中的注解 |
大多数时候,我们需要定义自己的注解,并编写自己的处理器去处理他们
注解处理器
如果没有又来读取注解的工具,那注解也不会比注释更有用。使用注解的过程中,很重要的一个部分就是创建与使用注解处理器。JavaSE5拓展了反射机制的API,以帮助我们构造这类工具。
下面例子是一个简单的注解处理器,我们将用它来读取被自定义注解修饰的demo类,并使用反射查找@AnnotationDemo1标记
public class AnnotationDemo2 {
public static void main(String[] args) {
AnnotationDemo1Tracker.handleAnnotation(demo.class);
}
}
class AnnotationDemo1Tracker{
public static void handleAnnotation(Class<?> cl){
for (Method method:cl.getDeclaredMethods()){
AnnotationDemo1 annotationDemo1 = method.getAnnotation(AnnotationDemo1.class);
if(annotationDemo1 != null){
System.out.println(method.getName()+"---id:"+annotationDemo1.id()+"---description:"+annotationDemo1.description());
}
}
}
}
上面例子中,注解处理器使用到了两个反射的方法:getDeclaredMethods()和getAnnotation(),它们都属于AnnotatedElement接口(Class、Method与Field等类都实现了该接口)。getAnnotation()方法返回指定类型的注解对象,如果目标方法上没有该类型的注解,则返回null。然后我们通过调用id()和description()方法从返回的注解中提取元素的值。
注解元素
注解中可用的元素类型如下所示:
- 所有基本类型
- String
- Class
- enum
- Annotation
- 以上类型的数组
如果我们使用了其他类型,那编译器就会报错。注意包装类型也不被允许。注解也可以作为元素的类型,也就是说注解可以嵌套。这是一个非常有用的特性。
默认值限制
编译器对元素的默认值有非常严格的要求。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。
其次,对于非基本类型的元素,不论是默认值还是声明时的值,都不能为null。这个约束使得处理器很难表现一个元素的存在或者缺失状态,为了绕开这个约束,我们只能自己定义一些特殊的值,比如空字符串或者负数,用来标识一个元素不存在。
生成外部文件
我们在使用hibernate、struts2以及早期的Spring框架时会发现我们除了编写代码以外,还需要配置一些额外的配置文件。这就导致我们使用外部配置文件时,相当于拥有了同一个类的两个单独的信息源,这经常导致代码的同步问题。同时,它也要求开发者必须同时指导如何编写Java程序,以及如何描述文件。