作为一个早期短暂从事过C++开发工作的程序员,我个人认为Annotation可能是Java与C++语言较大的不同点之一,这也是一个前C++程序员由衷认为Java可能、或许、maybe要比C++更好用的原因之一。二十多年来,Java一直保持着更新,不断完善并与时俱进,这可能是其多年来独领编程语言之风骚的重要原因。不多扯,入正题。(编程知识的学习,我一般会遵循这样的一个过程:先熟悉基本概念,再来个小程序跑起来看看,最后理论与程序相结合,加深认识并总结记录。)
1、什么是Annotation
Annotation被译作“注解”,标准的英译汉,但是这个译词并没有很好的反映Annotation在java中的意义,或许“标记”更好一些,也或许“标签”。
注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便地使用这些数据。——《Java编程思想》
注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。——《Java核心技术卷2》
从《Java核心技术卷2》的定义来看,注解包含两点内容:其一,它是标签、标记;此外,这个标签(标记)可以使用其他工具进行处理。
那么,其他工具在哪里可以处理这些标签呢?一个是源码层,另一个是类文件。
注解不会改变程序的编译方式,对于包含注解和不包含注解的代码,Java编译器会生成相同的虚拟机指令。——《Java核心技术卷2》
综上,大致可以给出Java注解一个简单通俗的描述使用过程:定义标签,然后提供标签处理工具,最后是应用标签到其它的代码中。当然,Java自带的注解,我们就只需要直接使用就可以了,因为定义和处理工具Java都帮我们做好了。
2、来个sample
2.1定义标签
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "No description";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
Target和Retention是java语言自带的两个元注解,Target表示用户自定义的注解(UseCase)能应用在哪里(注解类型声明,包,类或接口、方法等);
Retention用来指明自定义注解应该保留多长的时间(取值为SOURCE/CLASS/RUNTIME)
public @interface UseCase
这是定义注解的语法形式,乍一看类似接口的定义,区别在于interface前加了一个@符号,后续使用该用户自定义注解时语法就是@UseCase
public int id();
public String description() default "No description";
这是注解中定义字段的语法,()并不表示这是一个方法,再者,注解中不支持定义方法。
同时,可以设置字段的默认值,通过default关键字。没有默认值的字段必须在使用时显示指明。
2.2 标签处理工具
import java.lang.reflect.*;
public class UseCaseTracker {
public static void trackUseCase(Class<?> cl) {
for (Method m : cl.getMethods()) {
UseCase useCase = m.getAnnotation(UseCase.class);
if (useCase != null) {
System.out.println("Found Use Case : " + useCase.id() + " " + useCase.description());
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)中的RUNTIME使得注解可以保留到类文件中,并由虚拟机载入,这样我们就可以通过反射API来获得它们。接下来我就是通过反射API获得这些注解。
public static void trackUseCase(Class<?> cl) {
for (Method m : cl.getMethods()) {
UseCase useCase = m.getAnnotation(UseCase.class);
if (useCase != null) {
System.out.println("Found Use Case : " + useCase.id() + " " + useCase.description());
}
}
}
trackUseCase方法通过传入应用了UseCase注解的类,再通过反射API获得这些注解并打印注解中的信息。
2.3 应用标签
import java.util.List;
public class PasswordUtils {
@UseCase(id=47, description="Passwords must contain at least one numeric")
public boolean validatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}
@UseCase(id=48)
public String encryptPassword(String password) {
return new StringBuilder(password).reverse().toString();
}
@UseCase(id=49, description="New passwords can't equal previously used ones")
public boolean checkForNewPassword(List<String> prePasswords, String password) {
return !prePasswords.contains(password);
}
}
@Target(ElementType.METHOD)规定了注解只能是应用在方法上,因此,上述例子中便是如此。
@UseCase(id=47, description="Passwords must contain at least one numeric")
@UseCase(id=48)
@UseCase(id=49, description="New passwords can't equal previously used ones")
以上是三种使用应用自定义注解的例子。
2.4 测试
import java.lang.reflect.*;
public class UseCaseTracker {
public static void trackUseCase(Class<?> cl) {
for (Method m : cl.getMethods()) {
UseCase useCase = m.getAnnotation(UseCase.class);
if (useCase != null) {
System.out.println("Found Use Case : " + useCase.id() + " " + useCase.description());
}
}
}
public static void main(String[] args) {
trackUseCase(PasswordUtils.class);
}
}
直接在注解处理工具中加了个main方法来测试注解处理工具。输出结果如下:
Found Use Case : 47 Passwords must contain at least one numeric
Found Use Case : 49 New passwords can't equal previously used ones
Found Use Case : 48 No description
注:以上代码来自《Java编程思想》,部分有改动。
3、归纳整理
3.1 注解元素(属性)的类型
基本类型(int/short/long/byte/char/double/float/boolean)
String
Class
enum
注解类型
以上五种类型组成的数组
注:注解元素值不能为null,默认值也不能为null。
3.2 标准注解
3.3 Target注解的元素类型
元素类型定义在枚举类星Element中。