Java注解详解

JAVA 注解在Think in Java 这本书里是这样定义的

“注解(也成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据”。

这个定义有点儿抽象,而且根据我的经验,什么事情只要一跟“元”沾上边就变得复杂了,比如“元编程”。
我希望你尽快忘了这么晦涩的定义,下面我将给出一个简单的类比,帮你理解注解。
举个例子,比如你新爷的女儿有很多彩色小球,都散落在地上了,你新爷跟她说,把红色的球都放到箱子里。然后你新爷的女儿屁颠儿屁颠儿的就去找红色的球,然后放到箱子里。这个过程就是Java注解的工作过程。这个红色就是球的标签,JAVA注解就是一段代码的标签。

注解的用处:

  1. 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息。
  2. 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
  3. 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取。

基本注解。

JAVA1.8之前,内置了三个基本注解,1.7新增了@SafeVarargs,1.8新增了@FunctionalInterface
@Overried:表示当前的方法定义将覆盖超类中的方法。如果你不小心拼写错误,或者方法中签名对不上被覆盖的方法,编译器就会发出错误提示。
@Deprected: 如果你使用了该注解标记的元素,那么编译器会发出警告信息。
@SuppressWarnings: 关闭不当的编译器警告信息。
@SafeVarargs:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。
@FunctionalInterface:用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。

仅用上面的几种内置的基本注解显然不能满足我们日常开发需求,也不是JAVA提供注解的真正目的。我们日常开发过程中还是要定义自己的注解,帮助我们提高代码开发效率。

自定义注解

下面我们就通过自定义一个注解,来详细说说JAVA注解的知识点。

首先定义一个@FrankTest注解,我希望标注了@FrankTest标签的方法都执行一下,没有标注该注解的方法则不执行。

1
2
3
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FrankTest { }

就这么三行代码就定义完成了一个注解,如果没有@符号你还以为我定义了一个接口呢,除了@符号以外,相信你对@Target@Retention一定也很好奇。这两个是JAVA的元注解(注解的注解)。同样被称为元注解的还有@Documented@Inherited。如果你想自定义注解,就离不开这四个元注解。

元注解

@Target,表示该注解可以用于什么地方。

1
2
3
4
5
6
7
8
9
10
ElementType参数包括:
–CONSTRUCTOR,构造器的声明
–FIELD,域声明,包括[enum实例]
–LOCAL_VARIABLE, 局部变量声明
–METHOD,方法声明
–PACKAGE,包声明
–PARAMETER,参数声明
–TYPE:类,接口(包括注解类型)或enum声明
-TYPE_PARAMETER:Java1.8新增,表示该注解能写在类型参数的声明语句中
-TYPE_USE:Java1.8新增,能标注任何类型名称

@Retention,表示需要在什么级别保存该注解信息。

1
2
3
4
RetentionPolicy参数包括: 
–SOURCE,注解将被编译器丢弃
–CLASS,注解在class文件中可用 ,但会被VM丢弃[不读入内存]
–RUNTIME, VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息

@Documented,将此注解包含在Javadoc中。
@Repeatable,Java1.8新增,标记的注解可以多次应用于相同的声明或类型
@Inherited,允许子类型继承父类中的注解。

1
2
3
4
5
6
7
8
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}

@Test
public class A {}

public class B extends A {}

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。

回到刚才我们自定义的@FrankTest的例子,我们使用了@Target,其中ElementType是Method,表示该注解可以作用在方法上面,如果ElementType是TYPE,则表示可以作用在类上。@Retention表示在JVM运行时保留注解信息。

以上,介绍了如何定义一个注解,下面我们该使用注解了。看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
public class FrankAnnotationUtil {
@FrankTest
public void frankMethod(){
System.out.println("Frank");
}
@FrankTest
public void jackMethod(){
System.out.println("Jack");
}
public void maryMethod(){
System.out.println("Mary");
}
}

我在其中两个方法上面标注了注解,另外一个没有标注。我希望JVM能够运行frankMethod方法和jackMethod方法。那么你会问了,JVM怎么知道要怎么处理这些标记了@FrankTest的方法呢。答案就是注解处理器。这才关健

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FrankTestProcessor {
public static void processor(Class<?> tClass) throws IllegalAccessException, InstantiationException, InvocationTargetException {
for (Method method : tClass.getDeclaredMethods()){
FrankTest frankTest = method.getAnnotation(FrankTest.class);
Object object = tClass.newInstance();
if (frankTest != null){
System.out.println("Found FrankTest Annotation " + method.getName());
method.invoke(object);
}else {
System.out.println("Not Found FrankTest Annotation " + method.getName());
}
}
}

public static void main(String args[]) throws IllegalAccessException, InvocationTargetException, InstantiationException {
processor(FrankAnnotationUtil.class);
}
}

这段代码的输出是这样的

1
2
3
4
5
Not Found FrankTest Annotation maryMethod
Found FrankTest Annotation frankMethod
Frank
Found FrankTest Annotation jackMethod
Jack

标记标签的方法都执行了一次,没有标注的没有执行。其实没有什么神奇的,注解处理器的基本原理就是利用JAVA的反射原理。

在看另外一个例子,定义另外一个注解@FrankInfo,希望它能够提供更多信息。

1
2
3
4
5
6
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FrankInfo {
public int id();
public String describe() default "no description";
}

这个注解比上面那个多了int元素id,以及一个String元素description。

注解里面的元素可使用如下类型,所有的基本类型[int, float,boolean],String,Class,enum,Annotation以及以上类型的数组 ,如果你使用了其他类型,那编译器就会报错。注意,也不允许使用其他任何包装类型,不过由于自动打包的存在,这算不上是什么限制。注解也可以作为元素的类型,也就是说注解可以嵌套。

注解默认值限制

编译器对元素的默认值是有限制的。

  1. 元素不能有不确定的值。要么在元素定义的时候提供默认值,要么在使用注解时提供元素的值。
  2. 对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以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
17
public class FrankAnnotationUtil {

@FrankTest
@FrankInfo(id = 01,describe = "Frank's Method")
public void frankMethod(){
System.out.println("Frank");
}
@FrankTest
@FrankInfo(id = 02)
public void jackMethod(){
System.out.println("Jack");
}
@FrankInfo(id = 03,describe = "Mary's Method")
public void maryMethod(){
System.out.println("Mary");
}
}

JVM如何拿到注解标注中的信息呢?还是利用反射机制,看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public 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
3
Found FrankInfo 2 no description
Found FrankInfo 3 Mary's Method
Found FrankInfo 1 Frank's Method

以上就是有关Java注解的基本信息,以后日常开发中,可以任性的定义自己的注解了。

-------------本文结束-------------
坚持原创技术分享,您的支持将鼓励我继续创作!
0%