侧边栏壁纸
博主头像
日常流程导航博主等级

行动起来,活在当下

  • 累计撰写 11 篇文章
  • 累计创建 15 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

你真的会用注解吗

素秋云
2023-10-19 / 0 评论 / 0 点赞 / 15 阅读 / 9147 字

什么是注解

注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。《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接口呢,答案是可以的

public interface MyAnnotation2 extends Annotation {

}

发现编译后的字节码文件 一个是@interface定义的,一个是interface定义的,其他事一模一样的。

  • 当我们在使用的时候,发现直接用接口继承Annotation接口的MyAnnotation2是无法用@在方法上使用的,编译会报错。

所以我们自定义注解的时候,肯定是@interface来定义注解。

当我们定义一个注解的时候,里面并没有写任何的代码(即不含任何元素),这个注解就是个标记注解,如@Test,@Override等等

元注解

元注解其实就是注解的注解,在注解中使用元注解,更好的帮我们开发想要的功能代码。(PS: 定义注解的属性用括号结尾)

以JDK1.8为例,有五种元注解,如下:

  • @Target

表示注解的可以用于什么地方,可选参数是枚举ElementType中的属性

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文档

  • 利用注解类型检测能力,在代码编译前帮我们排错

0

评论区