注解在一定程度上是在把元数据与源代码文件结合在一起,而不是保存在外部文档中这一大的趋势下所催生的。注解可以提供用来完整的描述程序所需的信息,而这些信息是无法用Java来表达的。
因此,注解存储有关程序的额外信息,是可以由编译器来测试和验证的。注解还可以用来生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,我们可以将这些元数据保存在Java源代码中,并利用 annotation API 为自己的注解构造处理工具,同时注解的优点还包括:更加干净易读的代码以及编译器类型检查等。
注解的使用场景:
- 提供信息给编译器:编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理:软件工具可以利用注解信息来生成代码,HTML文档或其他相应处理
- 运行时的处理:某些注解可以在程序运行时接受代码的提取
1. 注解的分类
-
按运行机制划分
源码注解:只在源码中存在,编译成 .class 文件就不存在了
编译时注解:在源码和 .class 文件中都存在,像前面的 @Override、@Deprecated、@SuppressWarnings 都属于编译时注解
运行时注解:在运行阶段还有作用,甚至会影响运行逻辑,像 @Autowired 就属于运行时注解,它会在程序运行时把你的成员变量自动的注入进来 -
按来源划分
来自 JDK 的注解
来自第三方的注解
自定义注解 -
元注解
2. 元注解
负责注解的创建,是注解的注解。
- @Target
表示注解可以用在什么地方。ElementType可以是:
- TYPE:类,接口,枚举类上
- FIELD:字段上,包括枚举实例
- METHOD:方法上
- PARAMETER:参数前
- CONSTRUCTOR:构造函数上
- LOCAL_VARIABLE:局部变量上
- ANNOTATION_TYPE:注解类上
- PACKAGE:包上
- TYPE_PARAMETER:
- TYPE_USE:
可以是某一个值或者以逗号分隔的形式指定多个值,如果想要将注解应用于所有的ElementType,也可以省去 @Target 元注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
- @Retention
表示需要在什么级别上保留该注解信息。RetentionPolicy可以是:
- SOURCE:注解将被编译器丢弃
- CLASS:注解在class中可用,但会被VM丢弃
- RUNTIME:VM在运行期也将保留注解,因此可以通过反射机制读取注解信息
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
- @Documented
将此注解中的元素包含到javadoc中。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
- @Inherited
允许子类继承父类的注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
- @Repeatable
注解的值可以是多个,元素是一个容器注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}
3. 注解元素
- 基本语法
使用 @interface 关键字定义注解,在注解上添加元注解。一般还要为注解添加元素,没有元素的注解称为标识注解。
注解只有成员变量,没有方法。注解的成员变量在注解的定义中以"无形参的方法"形式来声明,其方法名定义了该成员变量的名字,返回值定义了该成员变量的类型。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnnotation {
int id() default -1;
String msg() default "Hello";
String value() default "";
}
- 注解元素可用的类型
- 所有基本类型(int,float,boolean等)
- String
- Class
- enum
- Annotation
- 以上类型的数组
如果使用了其他类型,那编译器就会报错。也不允许使用任何包装类型。注解也可以作为元素的类型,也就是说注解可以嵌套。
- 注解元素的默认值限制
编译器对注解元素的默认值有些过分挑剔。首先,注解元素不能有不确定的值。也就是说,注解元素要么具有默认值,要么在使用注解时设置元素值。
4. 内置注解
所有的注解都继承自 java.lang.annotation.Annotation 接口。
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
- @Override
表示当前的方法定义将覆盖超类中的方法。如果不小心拼写错误或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。
package java.lang;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
执行如下命令:
$ javac Override.java
$ javap -c Override.class
得到如下内容:
Compiled from "Override.java"
public interface java.lang.Override extends java.lang.annotation.Annotation {
}
由此可以看出,注解的本质就是一个继承了 Annotation 接口的接口。
@Override是一种典型的标记式注解。一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名,如果没有,自然不能编译通过。
编译器只能识别已经熟知的注解类,比如 JDK 内置的几个注解,而我们自定义的注解,编译器是不会知道这个注解的作用的,当然也不知道应该如何处理。
- @Deprecated
依然是一种标记式注解,永久存在,可以修饰所有类型,被标记的类、方法、字段等已经不再被推荐使用了,可能下一个版本就会删除。当然,编译器并不会强制要求你做什么,只是会在对象上画出一道线,建议你使用某个替代者。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={
CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
- @SuppressWarnings
抑制告警。它有一个 value 属性需要主动传值,传入需要被抑制的警告类型。
@Target({
TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)