注解


使用注解

注解(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越界

文章作者: 淡夜
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 淡夜 !
评论
  目录