Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。主要作用:Annotation其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息。如代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署,比如Junit矿建,Spring框架。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
内置的注解
java 定义了一套注解,共有 10 个,5 个在 java.lang 中,剩下5 个在 java.lang.annotation 中。
用于在代码的注解:
- @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings - 标注的额内容产生的警告,编译器会对这些警告保持静默。
- @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface - Java 8 开始支持,标识一个函数式接口。
用于在其他注解的注解(元注解):
- @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
- @Documented - 标记这些注解是否出现在用户文档中。即:如果定义Annotation类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。
- @Target - 标记这个注解可以标记哪种 Java 成员。
- @Inherited - 表示在子类中继承了父类中用此注解类型标注的注解,即:如果A继承了C,而C使用了B Annotation(定义B时使用了@Inherited修饰)修饰,则子类A将自动具有B注释。
- @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
在java.lang.annotation包中主要有三种重要类型:
1.Annotation
2.ElementType
public enum ElementType {
TYPE, //类、接口(包括注解接口)、枚举声明
FIELD,//字段(包括枚举常量)
METHOD,//方法声明
PARAMETER,//参数声明
CONSTRUCTOR,//构造方法声明
LOCAL_VARIABLE,//局部变量声明
ANNOTATION_TYPE,//注释类型声明
PACKAGE,//包声明
//ElementType.TYPE_USER和ElementType.TYPE_PARAMETER是Java 8新增的两个注解,
//用于描述注解的使用场景
TYPE_PARAMETER,//作为参数使用
TYPE_USE//作为类型使用
}
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder< @NonEmpty T > extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder< String > holder = new @NonEmpty Holder< String >();
@NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
}
}
3.RetentionPolicy
public enum RetentionPolicy {
SOURCE, //Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了
CLASS,//编译器将Annotation存储于类对应的.class文件中。默认行为
RUNTIME//编译器将Annotation存储于class文件中,并且可由JVM读入,可通过反射获取
}
(01) Annotation 就是个接口。
"每 1 个 Annotation" 都与 "1 个 RetentionPolicy" 关联,并且与 "1~n 个 ElementType" 关联。可以通俗的理解为:每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~n 个。
(02) ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。
"每 1 个 Annotation" 都与 "1~n 个 ElementType" 关联。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种用途。例如,若一个 Annotation 对象是 METHOD 类型,则该 Annotation 只能用来修饰方法。
(03) RetentionPolicy 是 Enum 枚举类型,它用来指定 Annotation 的策略。通俗点说,就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。
- 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override" 就没有任何作用了。
- 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是 Annotation 的默认行为。
- 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并且可由JVM读入,可以通过反射在运行时获取。
自定义Annotation
定义新的Annotation类型使用@interface关键字。
public @interface Login {
}
Annotation不仅可以是这种简单Annotation,Annotation还可以带成员变量,Annotation的成员变量在Annotation定义中必须以无参数方法的形式声明。其方法名和返回值定义了该成员的名字和类型。
/**
* 定义一个注解
*/
public @interface Login {
//定义两个成员变量
String username();
String password();
}
//一旦在Annotation里定义了成员变量之后,使用该Annotation时应该为该Annotation的成员变量指定值
class LoginTest{
//使用注解
@Login(username="lisi", password="111111")
public void login(){
//todo
}
}
//带有默认值的注解
public @interface Login2 {
//定义两个成员变量,以default为两个成员变量指定初始值
String username() default "zhangsan";
String password() default "123456";
}
class LoginTest2{
//因为它的成员变量有默认值,所以可以无须为成员变量指定值,而直接使用默认值
@Login
public void login(){
}
}
一旦在Annotation里定义了成员变量之后,使用该Annotation时应该为该Annotation的成员变量指定值,我们还可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字。如果为Annotation的成员变量指定了默认值,使用该Annotation则可以不为这些成员变量指定值,而是直接使用默认值。
根据我们介绍的Annotation是否可以包含成员变量,我们可以把Annotation分为如下两类:
- 标记Annotation: 一个没有成员定义的Annotation类型被称为标记。这种Annotation仅使用自身的存在与否来为我们提供信息。如前面介绍的@Override。
- 元数据Annotation:那些包含成员变量的Annotation,因为它们可接受更多元数据,所以也被称为元数据Annotation。
@java.lang.annotation.Target(java.lang.annotation.ElementType.FIELD)
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface MyAnnotation{
public String value();
}
//先反射获取字段,再通过字段获取注释,再通过注释获取值。
MyAnnotation fieldAnn = field.getAnnotation(MyAnnotation.class);
if (fieldAnn != null)
name = fieldAnn.value();
一个完整的自定义Annotation已经完。
提取Annotation的信息
在jdk1.5时,Java在java.lang.reflect包下新增了AnnotateElement接口,该接口代表程序中可以接受注释的程序元素,该接口主要有如下几个实现类(注意以下是类):
- Class:类定义。
- Constructor:构造器定义。
- Field:类的成员变量定义。
- Method:类的方法定义。
- Package:类的包定义
AnnotatedElement接口是所有程序元素(如Class、Method、Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor)之后,程序就可以调用该对象的如下三个等方法来访问Annotation信息:
- getAnnotation(Class<T> annotationClass); //返回该程序元素上存在的、指定类型的注释,如果该类型的注释不存在,则返回null。获取注释类型后就可以通过定义注释时的无参方法获取改注释的字段值。如上图。
- Annotation[] getAnnotations(); //返回该程序元素上存在的所有注释。
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass); //判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。