注解也被称为元数据,它是我们在代码中添加信息的一种形式化方法,使我们可以在稍后某个时刻非常方便地使用这些数据。注解是java se5中众多引入的变化之一。他们可以提供用来完整的描述程序所需的信息。而这些信息我们现在不能通过java的基本语法来表达。实际上,相比较使用java提供的注解,在使用中定义自己的新注解是更加常用的。
(一)java内置标准注解
java在它最核心的lang包中定义了三种标准的注解,这三个注解通过@符号就可以简单的使用了。
1.@Override
表示当前方法定义将覆盖超类中的方法。如果将方法拼写错误,或者签名对不上被覆盖的方法,编译器会报错。使用的方法非常的简单,我们就在覆盖的那个新方法上面一行添加这个注释就可以。增加了可读性。
2.@Deprecated
如果程序员使用了注解为他的元素,那么编译器会发出警告信息。就是说,被这个注解标记的方法是要被废弃的方法,我们不建议去使用它。我们下面用一个在Date中的构造器方法来举例(毕竟Date类已经基本全都是废弃方法,我们不需要注意下面方法中实际的作用,举例只是为了说明注解的用处)。和重写一样,在方法上一行写出。
@Deprecated
public Date(int year, int month, int date, int hrs, int min, int sec) {
int y = year + 1900;
// month is 0-based. So we have to normalize month to support Long.MAX_VALUE.
if (month >= 12) {
y += month / 12;
month %= 12;
} else if (month < 0) {
y += CalendarUtils.floorDivide(month, 12);
month = CalendarUtils.mod(month, 12);
}
BaseCalendar cal = getCalendarSystem(y);
cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.getDefaultRef());
cdate.setNormalizedDate(y, month + 1, date).setTimeOfDay(hrs, min, sec, 0);
getTimeImpl();
cdate = null;
}
3.@SuppressWarnings
关闭不当的编译器警告信息。在java se5之前这个注解是没作用的,现在我们使用他来表示忽略下面的警告。理论上使用频率并不是很高,我们的代码出现一些警告很正常,很少会为每个警告打上这个注解。
(二)java中的四种元注解
元注解专门负责注解其他的注解。看到这里,或许我们对这种概念还不太熟悉。但是没关系,我们先来看一下这四种元注解的作用,在介绍了之后,我们会去描述他们是怎么样使用的。
1.@Target
@Target表示该注解可以用于什么地方,可能的ElementType参数有以下这些种:
- CONSTRUCTOR:构造器的声明
- FIELD:域声明(包括enum实例)
- LOCAL_VARIABLE:局部变量声明
- METHOD:方法声明
- PACKAGE:包声明
- PARAMETER:参数声明
- TYPE:类、接口(包括注解类型)或enum声明
需要注意的是,如果我们在自定义的注解中不使用这个元注解,那么会默认该注解可以用于任何地方。
2.@Retention
@Retention表示需要在什么级别保存该注解信息。可选的RetentionPoilcy参数包括:
- SOURCE:注解将被编译器抛弃
- CLASS:注解在class文件中可用,但是在jvm中会被抛弃
- RUNTIME:jvm运行期间也会保留注解,因此可以通过反射机制读取注解信息
3.@Documented
@Documented将该注解包含在javadoc中。
4.@Inherited
@Inherited允许子类继承父类中的注解。
(三)自定义类型注解
每当你创建描述符性质的类或接口时,一旦其中包含了一些重复性的工作,那我们就可以考虑使用注解来简化与自动化这个过程,就像是spring中大量采用的那种方法。而且注解是真正的语言级别的概念,一旦构造出来,就享有编译期的类型检查保护。
1.一个自定义类型注解的例子
package Annotation;
import java.lang.annotation.*;
//几乎所有和注解有关的内容都在这个包内
@Target(ElementType.METHOD)
// 这里使用Target注解,并且我们设置为METHOD,表示CustomAnn这个注解作用于方法
@Retention(RetentionPolicy.RUNTIME)
// 这里使用Retention注解,设置为RUNTIME,表示这个注解的有效期为jvm中可存在
/**
*
* @author QuinnNorris
*
* 我们自定义一个叫做UseCase的注解类
*/
public @interface UseCase {
//这里不声明为class,而是使用@interface来表示注解
public int id();
//这是注解的属性,我们写成这种类似方法的写法。返回值表示了这个属性的类型。
public String description() default " no description";
//这个是另外一个属性,这个属性有默认值。
}
上面的这个例子已经很详细的诠释了一个注解应该如何定义。上面的例子中注解内有两个属性,我们也可以直接写一对大括号而不写任何的属性,我们将这样不写任何属性的注解称之为标记注解。
2.在相应的位置使用注解
我们既然已经给出了这个注解的实现情况,那么我们接下来继续看一下使用的情况:
package Annotation;
public class UsingAnno {
@UseCase(id=1,description="this is 1")
//用我们自定义创建的UseCase注解来标注one方法
public void one(){
//此处写方法中逻辑代码
}
@UseCase(id=2,description="this is 2")
//用我们自定义创建的UseCase注解来标注two方法
public void two(){
//此处写方法中逻辑代码
}
}
就是这样,我们可以为自己写的代码打上自定义的注解。
那么我们这样自定义注解,然后自己给自己写的代码打上注解有什么用呢?我们可以通过UseCase注解来跟踪一个项目中所有的用例(UseCase),如果一个方法或者一组方法实现了某个用例,那么程序员就可以为此方法加上这个注解。于是项目经理通过计算已经打上的注解数量,就知道了已经实现的用例数量,就可以很好的掌握项目的进展。如果要更新或者修改系统只要找到对应的用例即可。总而言之,注解将标注的作用达到了极致。
(四)注解处理器
我们在上面说到,注解可以被计算、可以用代码来统计和做更复杂的事情。这正是注解优于注释的地方。在使用注解的时候,最关键的就是创建和使用注解处理器。我们下面要实现一个注解处理器,处理我们上面创建和使用的UseCase注解。我们会需要用到java的反射机制。
java反射机制传送门:http://blog.csdn.net/quinnnorris/article/details/54809297
1.注解处理器实例
package Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
*
* @author QuinnNorris
*
* 注解处理器
*/
public class UseCaseTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 1, 2);
TestUseCase(useCases, UsingAnno.class);
}
private static void TestUseCase(List<Integer> useCases, Class<?> cl) {
// TODO Auto-generated method stub
for (Method m : cl.getDeclaredMethods()) {
// 通过反射的方法,获取这个类中所有的方法
UseCase uc = m.getAnnotation(UseCase.class);
// 实例化一个UseCase对象,将方法的注解存入
if (uc != null) {
System.out.println("useCase id = " + uc.id()
+ " ; description = " + uc.description());
useCases.remove(new Integer(uc.id()));
}
}
}
}
这是一种注解处理器,可能功能并没有想象中的那么复杂,但是对于只是想把功能展示一下已经足够了。这个方法中通过调用getDeclaredMethods方法获取了所有方法(包括由private修饰的方法),再去实例化一个注解类的对象,将获取这些方法的注解赋值给这个引用,我们再使用这个对象的属性进行我们想要进行的操作。这就完成了一个基本的注解处理器的功能。
2.注解元素的限制
在上例中,@UseCase注解由UseCase.java类来定义,其中包括int类型和String类型的元素,那么这些元素的类型有限制么?答案是:有的。
注解元素可用的类型:
1. 所有基本类型(int、float、boolean等)
2. String
3. Class
4. enum
5. Annotation
6. 以上所有类型的数组
理论上不可以使用包装类,但是有自动装箱拆箱的存在,使用包装类并不是问题。注解也可以作为元素的类型,也就是说注解可以嵌套。这是一个很有趣的技巧。
3.默认值设置
在自定义的注解中,java规定——元素不能有不确定的值。就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。对于那些非基本类型的元素,java规定——不能用null作为其值。就是说,有的时候,我们为了表示出一个元素不存在值,往往会先在定义的时候定义成空字符串或者负数。
也就是说,我们最开始的UseCase注解类,习惯上,我们要这样去书写:
package Annotation;
import java.lang.annotation.*;
//几乎所有和注解有关的内容都在这个包内
@Target(ElementType.METHOD)
// 这里使用Target注解,并且我们设置为METHOD,表示CustomAnn这个注解作用于方法
@Retention(RetentionPolicy.RUNTIME)
// 这里使用Retention注解,设置为RUNTIME,表示这个注解的有效期为jvm中可存在
/**
*
* @author QuinnNorris
*
* 我们自定义一个叫做UseCase的注解类
*/
public @interface UseCase {
//这里不声明为class,而是使用@interface来表示注解
public int id() default -1;
//这是注解的属性,我们写成这种类似方法的写法。返回值表示了这个属性的类型。
public String description() default "";
//这个是另外一个属性,这个属性有默认值。
}
4.注解不可被继承
不能使用关键字extends来继承某个@interface。或许有很多情况下,如果我们能这么做会带来很多的好处,但是事实上,这样做并不被允许,而且以后也可能一直不会被允许(没有人向java提出这种建议)。