注解的定义
注解,顾名思义,就是给程序打上一些标签(标签中包含了信息),以便在开发时根据这些标签获取重要的信息,根据获取的信息从而动态对程序的运行产生期望的结果。注解可以大幅度简化开发,故很多开发框架都使用了注解。注解的定义语法非常简单,代表一个注解的关键字是@interface,就是接口的关键字前面加一个@符号
@TestAnnotation //在类上使用注解
public class Test {
@TestAnnotation //在成员变量上使用注解
private int i;
@TestAnnotation //在成员变量上使用注解
private String s;
}
//定义注解
@interface TestAnnotation{
}
上面我们就定义了一个最简单的注解并使用了一下,但是这个注解里面空空如也,实际上注解里面是可以定义属性的,定义属性的格式是:属性类型 属性名()。如果有多个属性,用;分隔。注意,这里属性名后面加了一对小括号,但是并不是表示方法。下面我们尝试给刚才定义的TestAnnotation注解添加2个属性:
@TestAnnotation(f1 = 1,f2 = "class") //注解中必须显式对属性f1和f2赋值
public class Test {
@TestAnnotation(f1 = 2,f2 = "field")
private int i;
@TestAnnotation(f1 = 3,f2 = "field")
private String s;
}
@interface TestAnnotation{
int f1(); //定义int类型的属性f1
String f2(); //定义String类型的属性f2
}
可以看出,我们给注解定义了属性后,在使用注解时必须给所有属性显式赋值,否则编译报错。其实我们也可以在定义属性时用default关键字声明默认值,这样就可以在使用注解时不给属性显式赋值,代表使用默认值:
@TestAnnotation(f2 = "class") //使用注解时没有显式指定f1的值,代表使用默认值1
public class Test {
@TestAnnotation(f1 = 2,f2 = "field")
private int i;
@TestAnnotation(f1 = 3,f2 = "field")
private String s;
}
@interface TestAnnotation{
int f1() default 1; //定义int类型的属性f1,并声明默认值为1
String f2();
}
再补充一点,如果注解中只有1个属性,那么如果这个属性的名称为“value”,在使用注解给属性赋值时可以不写属性名,直接写属性值:
@TestAnnotation(1) //使用注解时指定value的值,无需写属性名value和=符号,直接写属性值即可
public class Test {
@TestAnnotation(2)
private int i;
@TestAnnotation(3)
private String s;
}
@interface TestAnnotation{
int value(); //定义int类型的属性value
}
元注解
上面我们在使用注解的时候,把注解用到了类和方法上,那么如何限定注解的作用范围呢?那就需要用到元注解了。元注解的含义是标记在注解上的注解,最重要的元注解有下面2种:
- @Target:表示注解可以标记在哪些地方,比如类、属性、接口、方法等等,它限定了注解的使用范围。我们看一下它的源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) //标明Target注解只能作用在注解上面
public @interface Target {
ElementType[] value(); //value属性,是一个ElementType类型的数组
}
分析下这段源码,可以看出注解Target中只有1个value属性,类型是ElementType数组。ElementType类型代表注解可以作用的范围,它的源码如下:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, //可以给一个类型进行注解,比如类、接口、枚举
/** Field declaration (includes enum constants) */
FIELD, //可以给属性进行注解
/** Method declaration */
METHOD, //可以给方法进行注解
/** Formal parameter declaration */
PARAMETER, //可以给一个方法内的参数进行注解
/** Constructor declaration */
CONSTRUCTOR, //可以给构造方法进行注解
/** Local variable declaration */
LOCAL_VARIABLE, //可以给局部变量进行注解
/** Annotation type declaration */
ANNOTATION_TYPE, //可以给一个注解进行注解
/** Package declaration */
PACKAGE, //可以给一个包进行注解
/**
* Type parameter declaration
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
* @since 1.8
*/
TYPE_USE,
/**
* Module declaration.
* @since 9
*/
MODULE
}
可以看出ElementType是一个枚举类,它的各种取值代表了Target注解可以作用于哪些地方。了解了这些之后,反过来再看Target注解的源码,发现Target给它自己的Class定义加上了一个@Target(ElementType.ANNOTATION_TYPE) 的注解,表示Target只能作用于注解。进而我们可以得出结论,任何元注解的类定义上必然有一个@Target(ElementType.ANNOTATION_TYPE) ,因为元注解只能作用于注解
- @Retention:它表示注解的保留时间,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME) //表示Retention注解运行时仍然保留
@Target(ElementType.ANNOTATION_TYPE) //标明Retention注解只能作用在注解上面
public @interface Retention {
RetentionPolicy value(); //value属性,RetentionPolicy
}
可以看出,Retention注解中只有1个value属性,类型为RetentionPolicy。同时由于Retention是1个元注解,它被标注了一个@Target(ElementType.ANNOTATION_TYPE)。
我们再看一下RetentionPolicy的源码:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE, //注解只在源码保留,编译时会被丢弃
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS, //注解会被记录到编译生成的.class文件中,但是运行时不会被加载到JVM中。这是默认的保留策略
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME //注解会被记录到编译生成的.class文件中,同时运行时会被加载到JVM中,因此在程序运行时可以通过反射获取到注解信息,这是我们自定义注解最常用的保留策略
}
可以看出RetentionPolicy是一个枚举类,它代表注解的保留策略,一共有3种取值。其中我们自定义注解最常用的是RetentionPolicy.RUNTIME,因为自定义注解通常目的是在运行的时候获取注解信息,从而控制程序行为,而只有RUNTIME策略会在运行时保留注解信息,从而能通过反射获取注解信息。
常见的JDK内置注解
接下来我们看几个常见的JDK内置注解,用前面的知识来分析一下它们
- @Deprecated:表示已过时
@Documented
//运行时保留
@Retention(RetentionPolicy.RUNTIME)
//可以作用于构造方法、属性、局部参数、方法、包、模块、参数、类、接口、枚举
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
String since() default ""; //since属性,表示在哪个版本后已过时
boolean forRemoval() default false; //forRemoval,表示是否会在后续版本中删除
}
日常我们感觉好像@Deprecated一般都用于标记方法,但实际上它的作用范围非常广,可以是构造方法、属性、局部参数、方法、包、模块、参数、类、接口、枚举等等
- @Override:表示子类对父类方法的重写
@Target(ElementType.METHOD) //作用于方法
@Retention(RetentionPolicy.SOURCE) //只在源码中保留,编译时抛弃
public @interface Override {
}
可以很容易分析出,由于@Override作用是标识子类对父类方法的重写,所以标记@Target(ElementType.METHOD)让它只作用于方法即可。同时它只是在源码阶段做一个重写的标志,无需在运行时获取,故标记@Retention(RetentionPolicy.SOURCE)源码保留即可。
- @FunctionalInterface:表示函数式接口,即只有1个抽象方法的接口
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
由于它作用于接口,故标记为@Target(ElementType.TYPE)
注解的实际使用举例
考虑一种场景:我们在使用json数据的时候,一般会将json格式的字符串映射为javabean对象,但是一些特殊的json数据难以直接形成这种映射,如{“1”:“baobao”,“2”:18},这个json字符串无法直接对应到javabean,因为java类的属性不能命名为1,2这样的数字。这时,我们就可以自定义一个注解,给javabean中的属性名打上注解,并将json的属性名信息包含在注解中就可以了。看具体代码:
@Retention(RetentionPolicy.RUNTIME) //运行时保留
@Target(ElementType.FIELD) //注解作用于类的成员变量
public @interface MyAnnotation {
//定义jsonFieldName属性,代表bean属性对应的json属性名
String jsonFieldName() default "";
}
这里我们定义了一个注解MyAnnotation,并将其作用域声明为ElementType.FIELD,并给它定义了一个jsonFieldName属性,代表bean属性所对应的json格式字符串属性名。然后我们定义一个javabean去应用这个注解:
public class Person {
//给属性name添加注解,代表对应json对象的名为"1"
@MyAnnotation(jsonFieldName = "1")
private String name;
//给属性age添加注解,代表对应json对象的名为"2"
@MyAnnotation(jsonFieldName = "2")
private int age;
public Person(){}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
可以看到我们给Person类的name和age属性都标记上了我们自定义注解MyAnnotation,并且将name属性的注解设置jsonFieldName = “1”,age属性的注解设置jsonFieldName = “2”,这样name和age属性就分别和json格式字符串{“1”:“baobao”,“2”:18}中的属性名对应上了。但是这样还仅仅是对应上,还需要在运行时通过反射获取注解信息,然后做相应的操作:
public class AnnotationResolver {
public static void main(String[] args) throws Exception {
//反射Person类
Class<?> clazz = Class.forName("baobao.annotation.Person");
Object instance = clazz.newInstance();
//遍历Person的所有属性
for (Field field : clazz.getDeclaredFields()) {
//打印属性名
System.out.println("field = " + field.getName());
//判断属性上是否有MyAnnotation注解
if (field.isAnnotationPresent(MyAnnotation.class)){
//得到注解
MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
//得到注解中属性的值
String jsonFieldName = annotation.jsonFieldName();
System.out.println("jsonFieldName = " + jsonFieldName);
//进行json属性映射到Person bean属性的操作。。。。
}
}
}
}
这里专门写了一个AnnotationResolver类对注解进行解析,主要思路是利用反射获取Person类的属性,然后判断属性上是否有MyAnnotation注解,如果有,得到注解并获取注解中jsonFieldName属性的值,这样一来Person的属性和json的属性名就对应起来了,接下来只要根据jsonFieldName(“1”,“2”)获取json格式字符串中对应的属性值(“baobao”,18),将这些属性值赋给Person对应的属性(name,age)值就可以了。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/2672.html