Java-Annotation补坑

前言

这东西也是以前没学过,但是接触到过,而且说实话网上的教程一篇比一篇难懂,所以还是打算自己写一篇来填坑。

初识

比如说你现在要重写Object类的hashCode方法,那么你可以在你重写的方法上面加上一个@Override的注释,这样编译器就会帮你去检查这个方法父类有没有等。

1577085694863

原理

首先我们先介绍一个接口,位于java.lang.annotation包下面的Annotation接口。它只有四个方法:

  • boolean equals(Object obj);
  • int hashCode();
  • String toString();
  • Class<? extends Annotation> annotationType();

显然前三个方法是从object继承来的。最后一个方法我们先放一下,只要知道有这么一个方法就行。

然后我们再找到有哪些类实现了这个接口,我这边用Override来举例。

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

可以看到,上面两行就是已经用到了Annotation,然后是Override的声明,到这里是不是还是奇奇怪怪看不太懂呢?没关系没关系,接着看。接下来是非常简单的两个枚举类,就是上面的ElementTypeRetentionPolicy

ElementType这个枚举类的代码比较简单,我就直接把理解扔到注释里了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */

FIELD, /* 字段声明(包括枚举常量) */

METHOD, /* 方法声明 */

PARAMETER, /* 参数声明 */

CONSTRUCTOR, /* 构造方法声明 */

LOCAL_VARIABLE, /* 局部变量声明 */

ANNOTATION_TYPE, /* 注释类型声明 */

PACKAGE, /* 包声明 */

TYPE_PARAMETER, /* 参数类型声明,java 8新增 */

TYPE_USE, /* 类型使用,java 8新增 */
}

还有一个RetentionPolicy类就更简单了:

1
2
3
4
5
6
7
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */

CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */

RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

每一个Annotation只能有一个RetentionPolicy来修饰,但是可以有多个(当然也可以是1个)ElementType来修饰。

像上面的Override用的@Target(ElementType.METHOD)说明这个Annotation是用来注释方法的,如果你用它来注释一个类的话,就会报错:

1577086996163

自定义一个注释

看完上面肯定还是有点不太了解,所以我们这里自己定义一个新的注释好了,名字就叫MyAnnotation。为了和之前的Override进行区别,我们这个注释用来注释一个类。

首先是基础框架:

1
2
public @interface MyAnnotation {
}

这里就声明了这个MyAnnotation是一个“注释”了,这里唯一会有疑问的是为什么interface前面会有一个@呢。如果使用@interface定义一个注解,那么就相当于声明实现了java.lang.annotation.Annotation接口,即该注解就是一个Annotation。这里和我们平时理解的接口完全不一样,因为Annotation接口的实现细节都由编译器完成。

然后我们学习Override的注释,在上面加上一行,就变成了:

1
2
3
@Target(ElementType.TYPE)
public @interface MyAnnotation {
}

现在你就可以用它去修饰一个类了,当然这毫无意义….因为这个注释什么功能都没有。

java自带注释

@Deprecated

被这个注释注释的方法在被使用的时候会有一条线,意味着这个方法已经不推荐使用了。

1577089201146

可以看一下这个注释的定义:

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

第一个说明生成的javadoc中会存在这个注释,第二个说明会将这个注释保存在字节码文件中,且会加载如jvm中。第三个就说明这个注释任何的部分都可以用,你可以在类、方法、构造器等之前使用它。

@Inherited

直接上源码:

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

这里它用的是ElementType.ANNOTATION_TYPE,这个的意思是注释的类型,也就是这个类只能被用来标注Annotation类型。也就是你只能在注释上面使用这个注释….也就是这个注释不能用来修饰方法、不能用来修饰类,它只能用来修饰注释,比如它可以注释我们刚刚自定义的那个注释…

1
2
3
4
@Target(ElementType.TYPE)
@Inherited
public @interface MyAnnotation {
}

这样MyAnnotation就有了可以被继承的能力。说实话这个注释我从来没用到过….

@SuppressWarnings

这个直译过来就是“压制警告”。源代码如下(删除了注释):

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}

可以发现这个注释里面居然有了一个叫value的字符串数组。显然这个字符串数组里面写的就是让编译器对“它所标注的内容”的某些警告保持静默。