注解
注解是 Java 5 所引入的众多语言变化之一。它们提供了 Java 无法表达的但是你需要完整表述程序所需的信息。因此,注解使得我们可以以编译器验证的格式存储程序的额外信息。注解可以生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,你可以将元数据保存在 Java 源代码中。并拥有如下有下优势:简单易读的代码,编译器类型检查,使用 annotation API 为自己的注解构造处理工具。即使 Java 定义了一些类型的元数据,但是一般来说注解类型的添加和如何使用完全取决于你。
- @Override:表示当前的方法定义将覆盖基类的方法。如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示。
- @Deprecated:如果使用该注解的元素被调用,编译器就会发出警告信息。
- @SuppressWarnings:关闭不当的编译器警告信息。
- @SafeVarargs:在 Java 7 中加入用于禁止对具有泛型varargs参数的方法或构造函数的调用方发出警告。
- @FunctionalInterface:Java 8 中加入用于表示类型声明为函数式接口
自定义注解例子
// annotations/UseCase.java import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase { int id(); String description() default "no description"; }
// annotations/PasswordUtils.java import java.util.*; public class PasswordUtils { @UseCase(id = 47, description = "Passwords must contain at least one numeric") public boolean validatePassword(String passwd) { return (passwd.matches("\\w*\\d\\w*")); } @UseCase(id = 48) public String encryptPassword(String passwd) { return new StringBuilder(passwd) .reverse().toString(); } @UseCase(id = 49, description = "New passwords can't equal previously used ones") public boolean checkForNewPassword( List<String> prevPasswords, String passwd) { return !prevPasswords.contains(passwd); } }
// annotations/PasswordUtils.java import java.util.*; public class PasswordUtils { @UseCase(id = 47, description = "Passwords must contain at least one numeric") public boolean validatePassword(String passwd) { return (passwd.matches("\\w*\\d\\w*")); } @UseCase(id = 48) public String encryptPassword(String passwd) { return new StringBuilder(passwd) .reverse().toString(); } @UseCase(id = 49, description = "New passwords can't equal previously used ones") public boolean checkForNewPassword( List<String> prevPasswords, String passwd) { return !prevPasswords.contains(passwd); } }
// annotations/UseCaseTracker.java import java.util.*; import java.util.stream.*; import java.lang.reflect.*; public class UseCaseTracker { public static void trackUseCases(List<Integer> useCases, Class<?> cl) { for(Method m : cl.getDeclaredMethods()) { UseCase uc = m.getAnnotation(UseCase.class); if(uc != null) { System.out.println("Found Use Case " + uc.id() + "\n " + uc.description()); useCases.remove(Integer.valueOf(uc.id())); } } useCases.forEach(i -> System.out.println("Missing use case " + i)); } public static void main(String[] args) { List<Integer> useCases = IntStream.range(47, 51) .boxed().collect(Collectors.toList()); trackUseCases(useCases, PasswordUtils.class); } }
输出
Found Use Case 48 no description Found Use Case 47 Passwords must contain at least one numeric Found Use Case 49 New passwords can't equal previously used ones Missing use case 50
注解的元素在使用时表现为 名-值 对的形式,并且需要放置在 @UseCase
声明之后的括号内。在 encryptPassword()
方法的注解中,并没有给出 description 的默认值,所以在 @interface UseCase 的注解处理器分析处理这个类的时候会使用该元素的默认值。
这个程序用了两个反射的方法:getDeclaredMethods()
和 getAnnotation()
,它们都属于 AnnotatedElement 接口(Class,Method 与 Field 类都实现了该接口)。getAnnotation()
方法返回指定类型的注解对象,在本例中就是 “UseCase”。如果被注解的方法上没有该类型的注解,返回值就为 null。我们通过调用 id()
和 description()
方法来提取元素值。注意 encryptPassword()
方法在注解的时候没有指定 description 的值,因此处理器在处理它对应的注解时,通过 description()
取得的是默认值 “no description”。
默认值限制--一般不会使用null
// annotations/SimulatingNull.java import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SimulatingNull { int id() default -1; String description() default ""; }
注解还有个常用地方就是单元测试
API 的提供方和框架将会将注解作为他们工具的一部分。通过 @Unit 系统,我们可以想象,注解会极大的改变我们的 Java 编程体验。