什么是注解
注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。《Thinking in Java》
怎么理解呢,注解好比一个职业的名称,比如老师这个名称类似一个注解,当我们听到老师这个职称,我们就知道他会教书育人;他是一个律师,我们的第一反应就是他很懂法律知识,注解类似一个标签,他告诉我们这个方法或者类有什么特征之类的,使我们更清楚的了解类或方法。
我们常见的注解,@Override表示这个方法重写了父类的方法;@Deprecated表示这个方法或者类过时了,不建议使用;@Test表示这个方法是测试方法。
如何定义注解
注解和类,接口一样,需要关键字@interface来定义。是的你没看错,和定义接口的interface差不多,只是在interface前面加了个@。
public @interface MyAnnotation {//定义注解
}
我们查看这个注解的字节码文件,发现定义的注解实际上实现了Annotation接口。
public interface Annotation {//Annotation接口源码
boolean equals(Object obj);
int hashCode();
String toString();
/**
*获取注解类型
*/
Class<? extends Annotation> annotationType();
}
那我们能不用@interface关键字,自己写一个接口继承Annotation接口呢,答案是可以的 ```java public interface MyAnnotation2 extends Annotation {
}
![image.png](https://cdn.nlark.com/yuque/0/2020/png/2439492/1607353072278-408db576-c8aa-47cf-868b-57a54d371db5.png#align=left&display=inline&height=140&margin=%5Bobject%20Object%5D&name=image.png&originHeight=140&originWidth=756&size=17145&status=done&style=none&width=756)<br />发现编译后的字节码文件 一个是@interface定义的,一个是interface定义的,其他事一模一样的。
- 当我们在使用的时候,发现直接用接口继承Annotation接口的MyAnnotation2是无法用@在方法上使用的,编译会报错。
![image.png](https://cdn.nlark.com/yuque/0/2020/png/2439492/1607353357953-5cec78ac-0f2c-4309-af00-d13aa6a96f72.png#align=left&display=inline&height=171&margin=%5Bobject%20Object%5D&name=image.png&originHeight=171&originWidth=405&size=13182&status=done&style=none&width=405)<br />**所以我们自定义注解的时候,肯定是@interface来定义注解。**<br />当我们定义一个注解的时候,里面并没有写任何的代码(即不含任何元素),这个注解就是个标记注解,如@Test,@Override等等。
<a name="mQBHP"></a>
### 元注解
元注解其实就是**注解的注解**,在注解中使用元注解,更好的帮我们开发想要的功能代码。(PS: 定义注解的属性用括号结尾)<br />以JDK1.8为例,有五种元注解,如下:
- **@Target**
表示注解的可以用于什么地方,可选参数是枚举ElementType中的属性
```java
public enum ElementType {
TYPE, //类,接口,枚举,注解的声明
FIELD,//域(属性字段或枚举常量)的声明
METHOD,//方法的声明
PARAMETER,//方法参数的声明
CONSTRUCTOR,//构造函数的声明
LOCAL_VARIABLE,//局部变量的声明
ANNOTATION_TYPE,//注解的声明
PACKAGE,//包的声明
TYPE_PARAMETER,//泛型的声明
TYPE_USE//此类型包括类型声明和类型参数声明
}
例子:
@Target(ElementType.CONSTRUCTOR)
public @interface MyAnnotation {//表示该注解作用于构造函数
}
一般使用的最多的是@Target(ElementType.TYPE)
- @Retention
表示注解的保留方式,可选参数是枚举RetentionPolicy中的属性
public enum RetentionPolicy {
SOURCE,//表示注解会在编译时被丢弃
CLASS,//默认策略,表示注解在class文件中可用,但是在运行时,不会被VM保留
RUNTIME//表示不仅会在编译后的class文件中存在,而且在运行时保留,因此它们主要用于反射场景,可以通过getAnnotation方法获取注解信息
}
例子:
首先定义三种保留方式的注解
//存在于class文件中,会被VM丢弃
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation {
String str();
}
//在运行时保留
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation2 {
String str();
}
//在编译时被丢弃
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation3 {
String str();
}
接下来写下测试方法,看结果
@MyAnnotation(str="1")
@MyAnnotation2(str="2")
@MyAnnotation3(str="3")
public class test {
public static void main(String[] args) {
Annotation[] annotations = test.class.getAnnotations();
for (Annotation annotation :annotations){
System.out.println("保留的注解:"+annotation.toString());
}
//输出: 保留的注解:@demo.a5.MyAnnotation2(str=2)
}
}
使用反射,我们可以得到运行时的注解属性,从输出结果看到只有@MyAnnotation2注解的属性输出了,因为它的元注解@Retention参数是RetentionPolicy.RUNTIME
- @Documented
此注解表示,将修饰的注解包含在Javadoc中,Javadoc工具会将此注解标记元素的注解信息包含在javadoc中。默认,注解信息不会包含在Javadoc中
- @Inherited
此注解表示,修饰的注解允许子类继承父类(PS:此注解只对注解标记的超类有效,对接口是无效的。)
@Inherited注解标记的注解,在使用时,如果父类和子类都使用的注解是同一个,那么子类的注解会覆盖父类的注解
例子:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String str();
}
@MyAnnotation(str = "222")
public class Father {
}
public class Son extends Father {
}
public class demo {
public static void main(String[] args){
son son = new son();
Annotation[] annotations = son.class.getAnnotations();
for (Annotation annotation :annotations){
System.out.println("保留的注解:"+annotation.toString());
}
//输出:保留的注解:@demo.a5.mytest(str=222)
}
}
从结果可以看出,子类能获取到父类注解的属性。
- @Repeatable(JDK1.8加入)
此注解表示,标记的注解可以多次应用于相同的声明或类型
例子:
@Repeatable(MyAnnotation2.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation{
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation2{
MyAnnotation[] value();
}
@MyAnnotation(value="1")
@MyAnnotation(value="11")
@MyAnnotation(value="111")
public class test {
public static void main(String[] args) {
Annotation[] annotations = test.class.getAnnotations();
for (Annotation annotation :annotations){
System.out.println("保留的注解:"+annotation.toString());
}
//输出:保留的注解:@demo.a5.MyAnnotation2(value=[@demo.a5.MyAnnotation(value=1), @demo.a5.MyAnnotation(value=11), @demo.a5.MyAnnotation(value=111)])
}
}
从结果可以看到,注解中的1,11,111都输出了,表示该注解可以多次应用于相同的声明或类型
自定义注解的使用
因为元注解@Retention(RetentionPolicy.RUNTIME)使我们能够用反射的方法,拿到注解的属性值,这样我们可以利用注解做很多事情,贯穿到我们的业务中去。
我们看以下例子,具体了解,怎么通过反射获取的。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface People {//定义注解
String name() default "";
int age() default 10;
}
public class Student {//定义Student类
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
@People(name="小张", age = 18)
public String toString() {
return "Student{" +
"info='" + info + '\'' +
'}';
}
}
//编写测试类
public class test {
public static void main(String[] args) {
getStudentInfo(Student.class);//参数为Studen类的class对象
}
public static void getStudentInfo(Class<?> clazz){
Method[] methods = clazz.getMethods();//通过反射获取Student类的所有方法
for (Method method : methods) {//便利所有方法
if (method.isAnnotationPresent(People.class)) {//判断注解是否为People注解
People annotation = method.getAnnotation(People.class);//获取该方法上的注解
System.out.println("姓名:"+annotation.name() + ",年龄:"+ annotation.age());//输出该注解的属性信息
}
}
}
}
注解的作用
- 灵活使用注解,穿插到我们的业务代码中去,能够大大的提高我们的开发效率
- 利用注解,帮我们生成Javadoc文档
- 利用注解类型检测能力,在代码编译前帮我们排错