详细介绍了Java中的注解的概念、用法、以及案例演示,比如自定义注解。
Java注解是附加在代码中的一些元数据形式,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。元注解包含在 java.lang.annotation 包中。
注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。
我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。
注解相关内部处理类,位于sun.reflect.annotation包。
注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。
定义注解类型元素时需要注意如下几点:
可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在后面的章节中可以看到:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。
但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。元注解位于 java.lang.annotation包中。
@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();
}
@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。如果在@Target注解中多次出现相同的枚举常量,那么这是一个编译时错误。
Target注解使用一个枚举类型定义如下可选的参数:
public enum ElementType {
/** 类,接口(包括注解类型)或枚举的声明 */
TYPE,
/** 属性的声明 */
FIELD,
/** 方法的声明 */
METHOD,
/** 方法形式参数声明 */
PARAMETER,
/** 构造方法的声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包的声明 */
PACKAGE
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段:
同样使用了RetentionPolicy枚举类型定义了三个阶段:
public enum RetentionPolicy {
/**
* 注解将被编译器忽略掉
*/
SOURCE,
/**
* 注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为
*/
CLASS,
/**
* 注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the containing annotation type for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class extends Annotation> value();
}
@Repeatable表示注解是否是可重复。@Repeatable 是 Java 1.8 才加进来的。其参数是注解类型的Class,表示该注解可以重复使用。
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}
表明一个字段引用的值可能来自于本地代码。是 Java 1.8 才加进来的,目前使用比较少。
首先,定义一个注解、和一个供注解修饰的简单Java类:
/**
* Target:表示ManAnnotation被限定能使用在类、接口或方法上面
* Documented:表示ManAnnotation可用于生成到JavaDoc文档中
*
* @author lx
*/
@Documented
@Target(value = {ElementType.TYPE, ElementType.METHOD})
public @interface ManAnnotation {
String name();
int age() default 20;
String[] tel();
}
public class Student {
@ManAnnotation(name = "stu", age = 15, tel = {"111111", "66666", "88888"})
public void learn(int times) {
for (int i = 0; i < times; i++) {
System.out.println("Start learning");
}
}
}
特殊语法一:
如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface FirstAnnotation {
}
//等效于@FirstAnnotation()
@FirstAnnotation
public class JavaBean{
//省略实现部分
}
特殊语法二:
如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
//省略实现部分
}
特殊用法三:
如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
//省略实现部分
}
特殊用法四:
如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。
其他:
注解保持力的三个阶段:Java源文件阶段;编译到class文件阶段;运行期阶段。只有当注解的保持力处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。
注解以及注解的类容可以通过反射获取。如果你不知道反射,可以看这篇博客:Java反射的深入理解以及超级详细的使用方式
/*获取Student的Class对象*/
Class stuClass = Student.class;
/*获取learn方法对象*/
Method learnMethod = stuClass.getDeclaredMethod("learn", int.class);
/*如果方法上有该注解类型,则获取注解*/
if (learnMethod.isAnnotationPresent(ManAnnotation.class)) {
System.out.println("Student类上配置了ManAnnotation注解!");
//获取该元素上指定类型的注解
ManAnnotation cherryAnnotation = learnMethod.getAnnotation(ManAnnotation.class);
System.out.println("name: " + cherryAnnotation.name() + ", age: " + cherryAnnotation.age()
+ ", tel: " + cherryAnnotation.tel()[0]);
} else {
System.out.println("Student类上没有配置ManAnnotation注解!");
}
解释一下:
如果我们要获得的注解是配置在方法上的,那么我们要从Method对象上获取;如果是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型上,需要从Class对象上去获取。总之在谁身上,就从谁身上去获取!
isAnnotationPresent(Class extends Annotation> annotationClass)方法是专门判断该元素上是否配置有某个指定的注解;
getAnnotation(Class< T > annotationClass)方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;
反射对象上还有一个方法getAnnotations(),该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。