使用注解
注解(Annotation)是Java语言用于工具处理的标注。
注解可以分为三类:
第一类是由编译器使用的注解,例如:
- @Override:让编译器检查该方法是否正确地实现了覆写;
- @SuppressWarnings:告诉编译器忽略此处代码产生的警告。
这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。
第二类是由工具处理.class文件使用的注解。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。
第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用。
注解的参数
- 配置参数必须是常量
- 注解的配置参数有默认值,缺少某个配置参数时将使用默认值。
- 大部分注解会有一个名为value的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数。
定义注解
Java语言使用@interface语法来定义注解(Annotation),它的格式如下:
public @interface Report {
int type() default 0;
String value() default "";
}可以用default设定一个默认值。最常用的参数应当命名为value。
元注解
有一些注解可以修饰其他注解,这些注解称为元注解。
@Target
使用@Target可以定义Annotation能够被应用于源码的哪些位置:
- 类或接口:ElementType.TYPE;
- 字段:ElementType.FIELD;
- 方法:ElementType.METHOD;
- 构造方法:ElementType.CONSTRUCTOR;
- 方法参数:ElementType.PARAMETER。
@Target定义的value是ElementType[]数组,只有一个元素时,可以省略数组的写法。
@Retention
@Retention定义了Annotation的生命周期:
- 仅编译期:RetentionPolicy.SOURCE;
- 仅class文件:RetentionPolicy.CLASS;
- 运行期:RetentionPolicy.RUNTIME。
value默认为Class。我们自己定义的注解一般都是runtime,所以要把value值设置为RetentionPolicy.RUNTIME。
@Repeatable
使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。
经过@Repeatable修饰后,就可以在同一处添加多个相同的注解。
@Inherited
使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效。
如何定义Annotation
示例:
- 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Init {
int value() default 23333;
}- 测试
public class Hello {
@Init
private int a;
public static void main(String[] args) {
try {
Field f = Hello.class.getDeclaredField("a");
f.setAccessible(true);
System.out.println(f.getAnnotation(Init.class).value());
//will print 23333
} catch (Exception e) {
e.printStackTrace();
}
}
}处理注解
处理注解,需要使用反射API。
判断某个注解是否存在于Class、Field、Method或Constructor:
- Class.isAnnotationPresent(Class)
- Field.isAnnotationPresent(Class)
- Method.isAnnotationPresent(Class)
- Constructor.isAnnotationPresent(Class)
使用反射API读取Annotation:
- Class.getAnnotation(Class)
- Field.getAnnotation(Class)
- Method.getAnnotation(Class)
- Constructor.getAnnotation(Class)
注意:读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。
使用注解
示例:
定义注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Range { int min() default 0; int max() default 255; }定义类
public class Person { @Range(min = 1, max = 20) public String name; @Range(max = 10) public String city; @Range(min = 1, max = 100) public int age; public Person(String name, String city, int age) { this.name = name; this.city = city; this.age = age; } @Override public String toString() { return String.format("{Person: name=%s, city=%s, age=%d}", name, city, age); } }测试
public class Main { public static void main(String[] args) throws Exception { Person p1 = new Person("Bob", "Beijing", 20); Person p2 = new Person("", "Shanghai", 20); Person p3 = new Person("Alice", "Shanghai", 199); for (Person p : new Person[] { p1, p2, p3 }) { try { check(p); System.out.println("Person " + p + " checked ok."); } catch (IllegalArgumentException e) { System.out.println("Person " + p + " checked failed: " + e); } } } static void check(Person person) throws IllegalArgumentException, ReflectiveOperationException { for (Field field : person.getClass().getFields()) { Range range = field.getAnnotation(Range.class); if (range != null) { Object value = field.get(person); // TODO: if (value instanceof String) { if (((String) value).length() < range.min() || ((String) value).length() > range.max()) throw new IllegalArgumentException(field.getName() + "越界"); } else if (value instanceof Integer) { if ((int) value < range.min() || (int) value > range.max()) throw new IllegalArgumentException(field.getName() + "越界"); } } } } }输出
Person {Person: name=Bob, city=Beijing, age=20} checked ok. Person {Person: name=, city=Shanghai, age=20} checked failed: java.lang.IllegalArgumentException: name越界 Person {Person: name=Alice, city=Shanghai, age=199} checked failed: java.lang.IllegalArgumentException: age越界