注解很重要,但是难以理解,刚开始学习的时候,就有很多的问题。
注解到底是什么?
我见过最精辟的理解是:注解可以看作标签。有了标签,我们对客观事物的理解会更透彻,理解起来也会比较容易。比如说这里有一群人,那我们心里肯定会犯嘀咕,这是一群什么人,在这里干什么?如果给这一群人打上一个标签,学生。是不是对这群人有了一定的了解,在这里可能是开会,春游,或者干其它的事情。
我加了一个注解之后,它是怎样做处理的?
我们可以使用反射,提取相关属性,从而进行处理。简单来说就是定义一个标签,贴到客观事物的身上,再对这个客观事物进行处理。
大体脉络就是这个,剩下的就是填充细节。
一,自定义注解
1)定义一个注解
public @interface Man {
String name();
int height();
int weight();
}
注解的@interface
特别像接口的interface
,只是前面多了一个@
。下面的name
,height
,weight
便是注解的属性。需要注意的是,这些属性可以是8种基本类型 ,String
类型 ,Enum
类型 ,Annotation
注解类型,以及Class
类型,当然也可以是它们的数组。
当然,一个注解也可以没有属性,这样的注解称为标识注解
。
public @interface Han {
}
public @interface Tang {
}
2)使用一个注解
使用上面的Man
注解为例
@Man(name = "LiSi", height = 180, weight = 120)
public class LiSi {
@Man(name = "0", height = 0, weight = 0)
int age;
@Man(name = "0", height = 0, weight = 0)
public void sayHello(){
System.out.println("Hello");
}
}
我们可以使用注解来修饰类,属性以及方法。但是看这代码感觉怪怪的,是不是有种想要限制注解使用范围的感觉,这是元注解要做的事,在后面我们会讲到。
@Tang
public class LiShiMing {
}
标识注解因为没有属性,所以我们可以省略括号直接使用。
还有一种注解,只有一个属性,且属性名字是value
的话,我们可以直接使用。
public @interface Age {
int value();
}
@Age(37)
public class BingBing {
}
当然也可以指定属性值来使用。
@Age(value = 61)
public class BenShan {
}
但是我又有了一个小问题,如果还是注解只有一个属性,但是这个属性名不为value
,那还可以这样使用吗?
public @interface Sex {
String sex();
}
可以清楚的看到,如果名称不为value
,必须明确指定。
public @interface People {
boolean genuis() default false;
}
当然一些属性有默认值,比如People
注解的genuis
属性,默认值为false
,我们在使用这个注解时,可以明确指定这个genuis
属性,也可以不指定,这样的话,它的值便是false
。
@People(genuis = true)
public class ZhangSan {
}
@People
public class MaLiu {
}
因为People
这个注解只有一个属性,且这个属性是默认的,所以我们可以不加括号直接使用。所以,如果我们遇到一个注解,它使用时可以直接使用,不加括号,不要直接认为这个注解是没有属性的,也可能它的属性是全部默认的,根据不同情况再对属性值做出改变。
二,元注解
元注解的概念我们在上面提到了,可以用元注解来修饰的注解,对注解做修正或者限定。
元注解总共有5种:@Retention
、@Documented
、@Target
、@Inherited
、@Repeatable
。
注解类型 | 含义 |
---|---|
@Retention | 注解保留时长 |
@Documented | 注解保留到JavaDoc中 |
@Target | 注解修饰的范围 |
@Inherited | 注解能够被自动继承 |
@Repeatable | java1.8加进来的,新特性,表示注解的值可以同时取多个 |
1)@Retention
注解的保留时长,取决于RetentionPolicy
,分三种。
保留时长 | 含义 |
---|---|
SOURCE | 只在源码阶段存在,编译器编译时将会丢失掉。 |
CLASS | 在编译阶段会保留,但是在运行时不会加载到JVM虚拟机中。当我们没有指定保留方式时,这是默认的保留方式。 |
RUNTIME | 注解在编译和运行时,会被保留,这是生命周期非常长的一种保留方式,我们可以通过反射读取注解的值。 |
@Retention(RetentionPolicy.SOURCE)
public @interface People {
boolean genuis() default false;
}
将注解的保留时长为运行时,此时可以通过反射获取到的,但是一般不建议,因为运行时注解,通过反射获取会造成一定运行效率的损耗。
2)@Documented
注解是否被例如JavaDoc
此类的工具文档化,只负责标记。在默认情况下,JavaDoc
是不包括注解的,但如果声明注解时指定了 @Documented
,则它会被 JavaDoc
之类的工具处理,,注解类型信息也会被包括在生成的文档中。
@Documented()
@Retention(RetentionPolicy.SOURCE)
public @interface People {
boolean genuis() default false;
}
3)@Target
注解修饰的范围,取决于ElementType,默认是包含全部范围。
元素类型 | 含义 |
---|---|
TYPE | 类,接口(包括注解),枚举的声明 |
FIELD | 属性字段(包括枚举常量) 声明 |
METHOD | 方法声明 |
PARAMETER | 参数申明 |
CONSTRUCTOR构造方法 | |
LOCAL_VARIABLE | 局部变量声明 |
ANNOTATION_TYPE | 注解声明 |
PACKAGE | 包声明 |
TYPE_PARAMETER | 类型参数,1.8之后 |
TYPE_USE | 使用类型,1.8之后 |
@Target(ElementType.TYPE)
@Documented()
@Retention(RetentionPolicy.SOURCE)
public @interface People {
boolean genuis() default false;
}
4)@Inherited
表示注解类型能够被自动继承,但它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited
注解的注解注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
@Inherited
@Target(ElementType.TYPE)
@Documented()
@Retention(RetentionPolicy.SOURCE)
public @interface People {
boolean genuis() default false;
}
@People
public class TestOne {
public class TestTwo {
}
}
People
注解TestOne
类,且TestTwo没有被其它注解注解,那么他的子类TestTwo
也被People
注解了。
5)@Repeatable
这是Java1.8
加进来的特性,一个注解可能有多个属性值,使用@Repeatable
便可以达到。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
public @interface Person {
String value();
}
@Person("Bad")
@Person("Good")
public class Man {
}
一个Man
可能是好人,也可能是坏人,它具有多个Person
注解,所有我们使用Repeatable
修饰Person
注解,而其中的内容便是一个Person
数组,即Persons
。
三,提取注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Explain {
String value();
}
public class Main {
public static void main(String[] args) throws Exception {
Class<?> clazz = Man.class;
//获取类的注解
if (clazz.isAnnotationPresent(Explain.class)) {
Explain Explain = clazz.getAnnotation(Explain.class);
System.out.println(Explain.value());
}
//获取字段注解
Field nameField = clazz.getDeclaredField("name");
if (nameField.isAnnotationPresent(Explain.class)) {
Explain nameFieldExplain = nameField.getAnnotation(Explain.class);
System.out.println(nameFieldExplain.value());
}
//获取构造函数注解
Constructor constructor = clazz.getConstructor(new Class[]{String.class});
if (constructor.isAnnotationPresent(Explain.class)) {
Explain constructorExplain = (Explain) constructor.getAnnotation(Explain.class);
System.out.println(constructorExplain.value());
}
//获取方法注解
Method getMethod = clazz.getMethod("getName", new Class[]{});
if (getMethod.isAnnotationPresent(Explain.class)) {
Explain getMethodExplain = getMethod.getAnnotation(Explain.class);
System.out.println(getMethodExplain.value());
}
Method setMethod = clazz.getMethod("setName", new Class[]{String.class});
if (setMethod.isAnnotationPresent(Explain.class)) {
Explain setMethodExplain = setMethod.getAnnotation(Explain.class);
System.out.println(setMethodExplain.value());
}
}
}
结果:
This is man
This is name
This is constructor
This is getName
This is setName
注解必须要有这个@Retention(RetentionPolicy.RUNTIME)
这个元注解,否则无法通过反射获取。
isAnnotationPresent()
顾名思义,判断是否是目标注解。
getAnnotation()
则是获取注解,从而获取属性值。