Java编程思想笔记七:注解
注解(也称元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。
Java SE5 内置了三种注解,定义在 java.lang 中:
- @Override,表示当前方法定义将覆盖基类中的方法,如果方法签名与被覆盖的方法不对应,编译器会报错;
- @Deprecated,表示当前元素即将弃用,程序中使用它时编译器会告警;
- @SuppressWarnnings,关闭不当的编译器警告信息。
1.使用注解
使用注解很简单,只需在要使用注解的元素前加 @ 后面跟要加的注解即可。例如使用 @Test 注解修饰 testExecute() 方法让其可运行:
public class TestTable {
@Test
void testExecute() { System.out.println("Executing..."); }
}
2.定义注解
我们除了可以使用 Java 内置的注解,还可以自定义注解。Java 提供了四种元注解专职负责注解其他的注解:
注解 | 描述 |
---|---|
@Target | 用来约束注解可以应用的地方(如方法、类或字段),可选的 ElementType 参数包括: CONSTRUCTOR:构造函数声明 FIELD:域声明(包括enum实例) LOCAL_VARIABLE:局部变量声明 METHOD:方法声明 PACKAGE:包声明 PARAMETER:参数声明 TYPE:类、接口(包括注解类型)或 enum 声明 TYPE_PARAMETER:Java8新增,类型参数 TYPE_USE:Java8新增,标注任意类型(不包括class) |
@Retention | 表示注解信息的保存级别,可选的 RetentionPolicy 参数包括: SOURCE:注解将被编译器丢弃。 CLASS:注解在class文件中可用,但会被VM丢弃。 VM将在运行期也会保留注解,可通过反射机制读取注解的信息。 |
@Decumented | 将此注解包含在 Javadoc 中 |
@Inherited | 允许子类继承父类中的注解 |
@Repeatable | Java 8新增的元注解,表示在同一个位置重复相同的注解 |
我们可以自定义一个 @UseCase 注解:
//: annotations/UseCase.java
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "no description";
}
定义 PasswordUtils 类使用 @UseCase 注解:
public class PasswordUtils {
@UseCase(id = 47, description = "Password must be number.")
public boolean validatePassword(String password) {
return (password.matches("/^[0-9]*$/g"));
}
}
使用自定义注解时需要注意 4 点:
- 注解元素可用类型有限
@UseCase 注解包含了两个元素:int 类型的 id 和 String 类型的 description。注解元素只支持一下几种类型:
- 所有基本类型(int,float,boolean 等)
- String
- Class
- enum
- Annotation (注解嵌套)
- 上述类型的数组
如果你使用了其它类型,编译器会报错。
-
使用注解时注解元素必须有默认值
注解的所有元素要么具有默认值,要么在使用时提供元素的值。此外,对于非基本类型的元素,无论是在源代码中声明时,还是在注解接口中定义默认值时,都不能以 null 作为元素的值。 -
使用快捷方式设置元素的默认值
当注解中只有一个元素需要赋值时,无需使用元素名-值的这种语法,只需在括号内给出该元素的值即可。例如,PasswordUtils 中使用注解 @UseCase 时,description 用默认值即可,那只有 id 需要赋值,则可以使用快捷方式: -
注解不支持继承,不能使用 extends 来继承某个 @interface
public class PasswordUtils {
@UseCase(47) // 快捷方式赋值:id = 47,description 使用默认值
public boolean validatePassword(String password) {
return (password.matches("/^[0-9]*$/g"));
}
}
3.编写注解处理器
大多时候,我们除了自定义注解,还需要编写自己的注解处理器来处理它们,这就需要用到反射机制。我们写一个简单的注解处理器来处理 PasswordUtils 类中的 @UseCase 注解:
public class UseCaseTracker {
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for (Method m : cl.getDeclaredMethods()) { // 反射:获取对象的所有方法
UseCase uc = m.getAnnotation(UseCase.class) // 反射:获取方法上指定的注解(@UseCase)
if (uc != null) {
System.out.println("Found Use Case:" + uc.id() + " " + uc.description())
useCases.remove(new Integer(uc.id()));
}
}
for (int i : useCases) {
System.out.println("Warning: Missing use case-" + i);
}
}
public static void main(String[] args) {
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47, 48, 49);
trackUseCases(useCases, PasswordUtils.class);
}
}
4.使用 apt 处理注解
注解处理工具 apt 是 Sun 为了帮助注解的处理过程而提供的工具。
与 javac 一样,apt 被设计为操作 Java 源文件,而不是编译后的类。apt 会在处理完源文件后编译它们,如果在系统构建过程中自动创建了新的源文件,该文件会在新一轮的注解处理过程中接受检查,apt 会一轮一轮地处理,直到不再有新的源文件产生为止,然后它再编译所有文件。
我们自定义的每个注解都需要自己的处理器,apt 工具能够很容易地将多个注解处理器组合在一起。这样,我们就可以指定多个要处理的类,比自己去遍历所有的类文件简单的多。
通过使用 AnnotationProcessorFactory apt 能够为每一个它发现的注解生成一个正确的注解处理器。在使用 apt 生成注解处理器时,无法使用 Java 的反射机制,因为我们操作的时源代码,而不是编译后的类。使用 mirror API 可以解决这个问题,它使我们能够在未经编译的源代码中查看方法、域以及类型。