注解和反射机制使用特别简单,但是它们在框架中被大量的使用,而如何灵活运用,想要深入理解框架,牢牢的掌握注解和反射机制的知识就显得极其的重要了。
注解
注解不同于注释,注释仅只用于写在源代码中,来使自己或者别人更容易的翻阅源代码。
注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。这些工具可以在源代码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。
注解不会改变程序的编译方式。Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。
注解主要用途有以下两点:
- 附属文件的自动生成,例如部署描述符或者bean信息类。
- 测试、日志、事务语义等代码的自动生成。
使用注解的前提下,首先我们应该知道注解本身不会做任何事情,它们只是存在于源文件中。编译器将它们置于类文件中,并且虚拟机会将它们载入。
注解的使用
注解接口
注解是由注解接口来定义的,语法格式如下:
修饰符 @interface 注解名{
元素类型声明1
元素类型声明2
...
}
每个元素类型声明语法格式如下(元素类型见下文):
元素类型 对象名(); //不带默认值就是类型的默认值
或者
元素类型 对象名() default value; //带默认值
举个栗子,下面的注解具有三个元素,id、name、age:
public @interface Test{
int id();
String name() default "小王";
int age() default 21;
}
所有的注解接口都隐性地扩展自java.lang.annotation.Annotation接口。这个接口是一个常规接口,不是一个注解接口。下表是这个接口的一些常用方法:
方法 | 用途 |
---|---|
Class<? extends Annotation> annotationType() | 返回Class对象,它用于描述该注解对象的注解接口。注意:调用注解对象上的getClass方法可以返回真正的类,而不是接口 |
boolean equals(Object other) | 如果other是一个实现了与该注解对象相同的注解接口的对象,并且如果该对象和other的所有元素彼此相等。那么返回True |
int hashCode() | 返回一个与equals方法兼容、由注解接口名以及元素值衍生而来的散列码 |
String toString() | 返回一个包含注解接口名以及元素值的字符串表示,例如,@Test(id=0,name=“小王”,age=21) |
所有的注解接口都直接扩展自java.lang.annotation.Annotation,我们不需要为注解接口提供实现类。
注解元素的类型为下列之一:
- 基础数据类型(byte、short、int、long、char、double、float或boolean)
- String
- Class(具有一个可选的类型参数,例如Class<? extends MyClass> )
- enum类型
- 注解类型
- 有前面所述类型组成的数组(由数组组成的多维数组不是合法的元素类型)
注解
注解的格式
每个注解都具有这种格式@注解名(元素名1=值1,元素名2=值2,....)
例如上文的注解他应该这种格式@Test(id=0,name="小王",age=21)
而元素的顺序无关紧要,@Test(name="小王",age=21,id=0)
这个注解和前面那个注解一样。
如果某个元素的值并未指定,那么就使用声明的默认值,或者元素类型的默认值。例如@Test(id=0)
,那么元素name的值就是字符串小王,元素age的值就是21。
注意事项:默认值并不是和注解存储在一起的;它们是动态计算而来的。例如,如果你将元素age的默认值改为21,然后重新编译Test接口,那么注释@Test(id=0)将使用这个新的默认值,甚至在那些在默认值修改之前就已经编译过的类文件中也是如此。
注解格式简化
有两个特殊的快捷方式可以用来简化注解。
- 如果没有指定元素,要么是因为注解中没有任何元素,要么是因为所有元素都使用默认值,那么你就不需要使用圆括号了。例如
@Test
和这个注解是一样的@Test(id=0,name="小王",age=21)
,这样的注解又称为标记注解。 - 另外一种快捷方式是
单值注解
。如果一个元素具有特殊的名字value,并且没有指定其他元素,那么你就可以忽略掉这个元素名以及等号。例如:
定义一个注解接口如下形式:
public @interface TestOneValue(){
String value();
}
那么,我们可以将这个注解书写成如下形式:
@TestOneValue("test")
而不是
@TestOneValue(value="test")
注解的使用
一个项可以有多个注解,例如:
@Test(id=0,name="小王",age=21)
@TestOneValue("test")
public void test01(){
}
如果注解声明为可重复的,那么我们就可以重复使用同一个注解:
@Test(id=0,name="小王",age=21)
@Test(id=1,name="小二",age=2)
public void test02(){
}
注意事项:一个注解元素永远不能设置为null,并且不允许其默认值为null。这样在实际应用中会相当不方便。你必须使用其他的默认值,泥例如“”或者Void.class。
如果元素值是一个数组,那么要将它的值用括号括起来,例如:@Test(...,score={100,101,102})
。
如果该元素只有一个值,那么可以忽略这些括号,例如:@Test(...,score=100)
,这个就和@Test(...,score={100})
一样。
既然个注解可以是另一个注解,那么就可以创建出任意复杂的注解,但是一般我们不这么用理解即可,例如:@Test(ref=@TestOneValue("test")
。
注意事项:在注解中引入循环依赖是一种错误。例如,因为Test具有一个注解类型为Reference的元素,所以Reference就不能再拥有一个类型为Test的元素。
注解各类声明
注解可以出现在许多地方,这些地方可以分为两类:声明和类型用法声明注解可以出现在下列声明处:
- 包
- 类(包括enum)
- 接口(包括注解接口)
- 方法
- 构造器
- 实例域(包含enum常量)
- 局部变量
- 参数变量
- 类型参数
对于类和接口,需要将注解放置在class和interface关键词的前面:
@Test
public class Student {
...}
对于变量,需要将它们放置在类型的前面:
@SuppressWarnings("unchecked")
int age;
泛型或者方法中的类型参数可以想下面这样被注解:
public class Cache<@Immutable V>{
...}
包是在文件package-info.java中注解的,该文件只包含以注解先导的包语句:
/**
Package-level Javadoc
*/
@GPL(version="3")
package cn.ac.whz.annotation;
import org.gnu.GPL;
注意事项:对局部变量的注解只能在源码级别上进行处理。类文件并不描述局部变量。因此,所有的局部变量注解在编译完一个类的时候就会被遗弃掉。同样的,对包的注解不能在源码级别之外存在。
注解类型用法
声明注解提供了正在被声明的项的相关信息。例如下面的声明中:
public User getUser(@NonNull String userId)
就断言userId参数不为空。
@NonNull注解是Checker Framework的一部分。通过使用这个框架,可以在程序中包含断言,例如某个参数不为空,或者某个String包含一个正则表达式。然后,静态分析工具将检查在给定的源代码段中这些断言是否有效。
现在,假设我们有一个类型为List<String>
的参数,并且想要表示其中所有的字符串都不为null。这就是类型用法注解大显身手之处,可以将该注解放置到类型参数之前:List<@NonNull String>
。
类型用法注解可以出现在下面的位置:
- 和泛型参数一起使用:List<@NonNull String>,Comparator.<@NonNull String> reverseOrder()
- 数组中的任何位置:@NonNull String[] [] words(word[i] [j]不为null),String @NonNull [] [] words(words不为null),String[] @NonNull [] words(word[i]不为null)
- 与超类和实现接口一起使用:class Warning extends @Localized Message
- 与构造器调用一起使用:new @Localized String(…)。
- 与强制类型和instanceof检查一起使用:(@Localized String) text,if (text instanceof @Localized String)。(这些注解只提供外部工具使用,它们对强制转型和instanceof检查不会产生任何影响)。
- 与异常规约一起使用:public String read() throws @Localized IOException
- 与通配符和类型边界一起使用:List<@Localized ? extends Message>,List<? extends @Localized Message>
- 与方法和构造器引用一起使用:@Localized Message::getText