ruby woo's blog
write the code, change the world!
注解就这么简单

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。 -- 选自菜鸟教程

1. 什么是注解

注解可以理解为一种标签,就像说到篮球就会想到蔡徐坤,说到宽面就会想到吴亦凡一样,篮球、宽面就是他们的"标签",就是他们的代名词。
那么什么又是标签呢?
它是对某些事物行为的从某些角度的评价与解释。

那么注解就是对类或者对方法的解释、标记

1.1 注解的语法

注解也是一种类型,跟 class、interface 一样

1.1.1 注解定义

注解通过 @interface 关键字定义

public @interface TestAnnotation{

}

创建注解跟创建接口的语法类似,只不过多了一个@符号,上述代码创建了一个名为TestAnnotation 注解。

1.1.2 使用注解

我们可以在类,字段,方法上使用注解

@TestAnnotation
public class Test{

}

创建了一个名为Test的类,我们在这个类上加了自定义的注解 @TestAnnotation

不过我们加的这个注解还没有任何的作用,要想注解起到作用,我们下面还要了解 元注解

2. 元注解

元注解是可以注解到注解上的注解,或者说它是一种基本注解,可以在其他注解上使用。元注解也是一种标签,但是它是一张特殊的标签,用来解释说明其他标签的。元注解有一下五种:

  • @Retention
  • @Documented
  • @Target
  • @Inherited
  • @Repeatable

@Retention

Retention 是保留的意思。当 @Retention 应用到某个注解上的时候,它解释说明了被标注的注解的作用时间。

它有三个取值:

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们

我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

}

上述代码中,我们指定 TestAnnotation 可以在程序运行周期被获取到,因此它的生命周期非常的长。

@Documented

顾名思义,这个元注解和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Target

Target 是目标的意思,@Target 指定了注解运用的地方。当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值:

  • ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
  • ElementType.CONSTRUCTOR 可以给构造方法进行注解
  • ElementType.FIELD 可以给属性进行注解
  • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
  • ElementType.METHOD 可以给方法进行注解
  • ElementType.PACKAGE 可以给一个包进行注解
  • ElementType.PARAMETER 可以给一个方法内的参数进行注解
  • ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface TestAnnotation{

}

上述代码中,我们指定 TestAnnotation 可以在程序编译周期被获取到,并且该注解可以作用在类、接口、枚举上。

@Inherited

Inherited是继承的意思,如果一个基类被 @Inherited 注解过的注解 注解的话,那么如果这个基类的子类没有被任何注解 注解的话,那么该子类就继承了基类的注解。说起来太抽象了,用代码演示一下

// 定义一个注解 该注解被 @Inherited 修饰
@Inherited
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface TestAnnotation{

}

// 定义一个类 使用自定义注解
@TestAnnotation
public class A {}


// 类B继承类A
public class B extends A {}

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

3. JDK7后增加的注解

  • @SafeVarargs 忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。Java7 开始支持。

  • @Repeatable 顾名思义,这个注解可以加在同一个地方多次。 Java8 开始支持。

  • @FunctionalInterface 标识一个匿名函数或者函数式接口。 Java8 开始支持。

4. 编译阶段注解

  • @Override 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings 指示编译器去忽略注解中声明的警告。

5. 注解的属性

注解的属性也叫成员变量,它只有成员变量没有方法,注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int id();
    String message();
}

上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。

赋值的方式是在注解的括号内以 xx=”” 形式,多个属性之前用 ,隔开。

@TestAnnotation(id=1, message="hello annotation")
public class C {}

每次使用 @TestAnnotation 注解都要对每个成员变量赋值,非常麻烦,我们也可以对其设置默认值。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int id() default 123;  // id默认值为123
    String message() default "hello world";// message默认值是 hello world
}

那么我们就可以直接使用:

@TestAnnotation // 注解设置了默认值,可不写 xx=xx
public class D {}

还有一种情况是 注解只有一个成员方法 如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String message(); // 他只有一个message
}

我们可以直接这样用:

@TestAnnotation("hello") // 相当于 @TestAnnotation(message="hello")
public class E{}

6. 注解与反射

我们已经知道了怎么定义注解以及怎么使用注解,可是我们的注解还没有起到作用。也就是说我们只是在自己的类、方法上添加了自己的注解,但是它就在那而已并没有起到应有的作用。

我们要向真正的使用注解,离不开Java另一特性 —— 反射。

有关反射的更多知识请关注本站博文 —— 反射就这么简单