JAVA 注解在Think in Java 这本书里是这样定义的
“注解(也成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据”。
这个定义有点儿抽象,而且根据我的经验,什么事情只要一跟“元”沾上边就变得复杂了,比如“元编程”。
我希望你尽快忘了这么晦涩的定义,下面我将给出一个简单的类比,帮你理解注解。
举个例子,比如你新爷的女儿有很多彩色小球,都散落在地上了,你新爷跟她说,把红色的球都放到箱子里。然后你新爷的女儿屁颠儿屁颠儿的就去找红色的球,然后放到箱子里。这个过程就是Java注解的工作过程。这个红色就是球的标签,JAVA注解就是一段代码的标签。
注解的用处:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息。
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取。
基本注解。
JAVA1.8之前,内置了三个基本注解,1.7新增了@SafeVarargs
,1.8新增了@FunctionalInterface
@Overried
:表示当前的方法定义将覆盖超类中的方法。如果你不小心拼写错误,或者方法中签名对不上被覆盖的方法,编译器就会发出错误提示。@Deprected
: 如果你使用了该注解标记的元素,那么编译器会发出警告信息。@SuppressWarnings
: 关闭不当的编译器警告信息。@SafeVarargs
:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。@FunctionalInterface
:用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。
仅用上面的几种内置的基本注解显然不能满足我们日常开发需求,也不是JAVA提供注解的真正目的。我们日常开发过程中还是要定义自己的注解,帮助我们提高代码开发效率。
自定义注解
下面我们就通过自定义一个注解,来详细说说JAVA注解的知识点。
首先定义一个@FrankTest
注解,我希望标注了@FrankTest
标签的方法都执行一下,没有标注该注解的方法则不执行。1
2
3 ({ElementType.METHOD})
(RetentionPolicy.RUNTIME)
public FrankTest { }
就这么三行代码就定义完成了一个注解,如果没有@符号你还以为我定义了一个接口呢,除了@符号以外,相信你对@Target
,@Retention
一定也很好奇。这两个是JAVA的元注解(注解的注解)。同样被称为元注解的还有@Documented
,@Inherited
。如果你想自定义注解,就离不开这四个元注解。
元注解
@Target
,表示该注解可以用于什么地方。1
2
3
4
5
6
7
8
9
10ElementType参数包括:
–CONSTRUCTOR,构造器的声明
–FIELD,域声明,包括[enum实例]
–LOCAL_VARIABLE, 局部变量声明
–METHOD,方法声明
–PACKAGE,包声明
–PARAMETER,参数声明
–TYPE:类,接口(包括注解类型)或enum声明
-TYPE_PARAMETER:Java1.8新增,表示该注解能写在类型参数的声明语句中
-TYPE_USE:Java1.8新增,能标注任何类型名称
@Retention
,表示需要在什么级别保存该注解信息。
1 | RetentionPolicy参数包括: |
@Documented
,将此注解包含在Javadoc中。@Repeatable
,Java1.8新增,标记的注解可以多次应用于相同的声明或类型@Inherited
,允许子类型继承父类中的注解。
1 |
|
注解 Test 被 @Inherited
修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
回到刚才我们自定义的@FrankTest
的例子,我们使用了@Target
,其中ElementType是Method,表示该注解可以作用在方法上面,如果ElementType是TYPE,则表示可以作用在类上。@Retention
表示在JVM运行时保留注解信息。
以上,介绍了如何定义一个注解,下面我们该使用注解了。看代码
1 |
|
我在其中两个方法上面标注了注解,另外一个没有标注。我希望JVM能够运行frankMethod方法和jackMethod方法。那么你会问了,JVM怎么知道要怎么处理这些标记了@FrankTest的方法呢。答案就是注解处理器。这才关健
1 | public class FrankTestProcessor { |
这段代码的输出是这样的
1 | Not Found FrankTest Annotation maryMethod |
标记标签的方法都执行了一次,没有标注的没有执行。其实没有什么神奇的,注解处理器的基本原理就是利用JAVA的反射原理。
在看另外一个例子,定义另外一个注解@FrankInfo
,希望它能够提供更多信息。
1 | (ElementType.METHOD) |
这个注解比上面那个多了int元素id,以及一个String元素description。
注解里面的元素可使用如下类型,所有的基本类型[int, float,boolean],String,Class,enum,Annotation以及以上类型的数组 ,如果你使用了其他类型,那编译器就会报错。注意,也不允许使用其他任何包装类型,不过由于自动打包的存在,这算不上是什么限制。注解也可以作为元素的类型,也就是说注解可以嵌套。
注解默认值限制
编译器对元素的默认值是有限制的。
- 元素不能有不确定的值。要么在元素定义的时候提供默认值,要么在使用注解时提供元素的值。
- 对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为其值。
为了表示元素确实为空这种定义,我们可以使用一些其他自定义的特殊值来表示某个元素不存在,比如空字符串或者负数。例如1
2
3
4
5
6@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimulatingNull {
public int id() default -1;
public String description() default "";
}
最后,注解不支持继承。不能使用extends来继承某个@interface
回到上面的例子,我们定义了包含元素的注解,那么如何使用注解呢,看例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class FrankAnnotationUtil {
01,describe = "Frank's Method") (id =
public void frankMethod(){
System.out.println("Frank");
}
02) (id =
public void jackMethod(){
System.out.println("Jack");
}
03,describe = "Mary's Method") (id =
public void maryMethod(){
System.out.println("Mary");
}
}
JVM如何拿到注解标注中的信息呢?还是利用反射机制,看代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14public class FrankInfoProcessor {
public static void processor(Class<?> cl){
for (Method method : cl.getDeclaredMethods()){
FrankInfo frankInfo = method.getAnnotation(FrankInfo.class);
if (frankInfo != null){
System.out.println("Found FrankInfo " + frankInfo.id() + " " + frankInfo.describe());
}
}
}
public static void main(String[] args){
processor(FrankAnnotationUtil.class);
}
上面代码输出如下内容:1
2
3Found FrankInfo 2 no description
Found FrankInfo 3 Mary's Method
Found FrankInfo 1 Frank's Method
以上就是有关Java注解的基本信息,以后日常开发中,可以任性的定义自己的注解了。