Java进阶之深入理解注解(Annotation)

1 注解

1.1 什么是注解?

答:注解(Annotation)在JDK1.5之后增加的一个新特性,注解的引入意义很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用注解。注解作为程序的元数据嵌入到程序,对运行​​代码的操作没有影响注解可以被解析工具或编译工具解析,此处注意注解不同于注释(comment)。当一个接口直接继承java.lang.annotation.Annotation接口时,仍是接口,而并非注解。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

1.2 什么是元注解?

答:元注解就是可以注解到别的注解上的注解,如下的@Target。被注解的注解称之为组合注解,组合注解具备其上元注解的功能。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

1.3 什么是元数据?

答:元数据以标签的形式存在Java代码中,它的存在并不影响程序代码的编译和执行,通常它被用来生成其它的文件或运行时知道被运行代码的描述信息。Java当中的Javadoc和注解都属于元数据

2 四种元注解

在JDK中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解),他们分别是:

  • @Target:描述注解的使用范围(即被修饰的注解可以用在什么地方)
  • @Retention:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)
  • @Inherited:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)
  • @Documented:描述在使用 Javadoc 工具为类生成帮助文档时,保留其注解信息

2.1 @Target注解

(1)作用是:描述注解的使用范围(即被修饰的注解可以用在什么地方)

(2)目的是:用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 包、type(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。

(3)Target注解的源码

@Documented  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.ANNOTATION_TYPE)  
public @interface Target {  
    ElementType[] value();  
} 
public enum ElementType {
    TYPE, // 类、接口、枚举类
    FIELD, // 成员变量(包括:枚举常量)
    METHOD, // 成员方法
    PARAMETER, // 方法参数
    CONSTRUCTOR, // 构造方法
    LOCAL_VARIABLE, // 局部变量
    ANNOTATION_TYPE, // 注解类
    PACKAGE, // 可用于修饰:包
    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
2.1.1 重复注解@Repeatable

(1)作用是:重复注解,允许在同一申明类型(类,属性,或方法)前多次使用同一个类型注解

(2)在Java8以前,同一个程序元素前最多只能有一个相同类型的注解;如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。 Java8之前的做法:

public @interface Roles {
    Role[] roles();
}
public @interface Roles {
    Role[] value();
}
public class RoleAnnoTest {
    @Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")})
    public String doString(){
        return "";
    }
}

在这里插入图片描述

(3)Java8之后增加了重复注解,使用方式如下:

// @Repeatable,指向存储注解 Roles,在使用时候,直接可以重复使用 Role 注解
@Repeatable(Roles.class)
public @interface Role {
    String roleName();
}
public @interface Roles {
    Role[] value();
}
class RolesTest {
    @Role(roleName = "role1")
    @Role(roleName = "role2")
    public String doString() {
        return "";
    }
}

不同的地方是,创建重复注解 Role 时,加上@Repeatable,指向存储注解 Roles,在使用时候,直接可以重复使用 Role 注解。从上面例子看出,Java 8里面做法更适合常规的思维,可读性强一点。但是,仍然需要定义容器注解
在这里插入图片描述

2.1.2 类型注解

(1)TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中

(2)TYPE_USE:表示注解可以再任何用到类型的地方使用,无处不在的注解,可以让编译器执行更严格的代码检查,从而提高程序的健壮性

创建对象(用 new 关键字创建)
类型转换
使用 implements 实现接口
使用 throws 声明抛出异常

(3)效果如下截图
在这里插入图片描述

2.2 @Retention注解

(1)作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)

(2)目的是:用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略

@Retention(RetentionPolicy.SOURCE)

(3)三种策略定义在RetentionPolicy枚举中

public enum RetentionPolicy {
    SOURCE,   // 仅存在Java源文件
    CLASS,    // 存在经编译器后生成的Class字节码文件
    RUNTIME   // 保留在运行时VM中
}
  • 生命周期长度:SOURCE < CLASS < RUNTIME ,前者能作用的地方后者一定也能作用
  • 如果需要在运行时通过反射去动态获取注解信息,那只能用 RUNTIME 注解
  • 如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用CLASS注解(比如ButterKnife的@BindView)
  • 如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解

2.3 @Inherited

(1)作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)

(2)通过代码来进行验证,创建一个自定义注解

@Target({ElementType.TYPE})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInherited {
}
@MyInherited
class MyInheritedParent {
}
class MyInheritedB extends MyInheritedParent {
}

执行方法,从控制台中可以看到打印的信息:

System.out.println(TAG + " " + MyInheritedParent.class.getAnnotation(MyInherited.class));
System.out.println(TAG + " " + MyInheritedB.class.getAnnotation(MyInherited.class));
// AnnotationParser @com.read.kotlinlib.annotation.MyInherited()
// AnnotationParser @com.read.kotlinlib.annotation.MyInherited()

2.4 @Documented

(1)作用是:描述在使用 Javadoc 工具为类生成帮助文档时,保留其注解信息

(2)验证@Documented的作用,我们创建一个自定义注解和测试类

@Documented   // @Documented
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyDocumentedt {
    public String value() default "这是@Documented注解为文档添加的注释";
}
public class MyDocumentedTest {
    /**
     * 测试 document
     * @return String the response
     */
    @MyDocumentedt
    public String test(){
        return "测试document";
    }
}

打开java文件所在的目录下,打开命令行输入:

javac .\MyDocumentedt.java .\MyDocumentedTest.java
javadoc -d doc .\MyDocumentedTest.java .\MyDocumentedt.java

在这里插入图片描述
打开生成的doc文件夹,打开index.html,发现在类和方法上都保留了 MyDocumentedt 注解信息
在这里插入图片描述

3 内建注解

Java提供了多种内建的注解,下面接下几个比较常用的注解:@Override(覆写)、@Deprecated(不赞成使用)、@SuppressWarnings(压制警告)这3个注解。

3.1 @Override(覆写)

// @Override可适用元素为方法,仅仅保留在java源文件中
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

用途:用于告知编译器,我们需要覆写超类的当前方法。如果某个方法带有该注解但并没有覆写超类相应的方法,则编译器会生成一条错误信息

3.2 @Deprecated(不赞成使用)

// @Deprecated可适合用于除注解类型声明之外的所有元素,保留时长为运行时VM
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

用途:用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。

3.3 @SuppressWarnings(压制警告)

// @SuppressWarnings可适用于除注解类型声明和包名之外的所有元素,仅仅保留在java源文件中。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

用途:用于告知编译器忽略特定的警告信息,例在泛型中使用原生数据类型

该注解有方法value(),可支持多个字符串参数,例如:

@SupressWarning(value={"uncheck","deprecation"})

@Override,@Deprecated都是无需参数的,而@SuppressWarnings是需要带有参数的,可用参数如下:
在这里插入图片描述

4 自定义注解

(1)自定义注解

@Target({ElementType.TYPE, ElementType.METHOD})
// 由于该注解的保留策略为RetentionPolicy.RUNTIME,故可在运行期通过反射机制来使用,否则无法通过反射机制来获取
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAnno {
    /**
     * 注解方法不带参数,比如name(),website();
     * 注解方法返回值类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型
     * 注解方法可有默认值,比如default "chen.com",默认website="chen.com"
     * @return
     */
    String name();
    String website() default "chen.com";
    int revision() default 1;
}

(2)使用自定义注解的实体类

public class AnnotationDemo {
    @AuthorAnno(name = "chen", website = "chen.com", revision = 1)
    public static void main() {
        System.out.println("I am main method");
    }
    @SuppressWarnings({"unchecked", "deprecation"})
    @AuthorAnno(name = "chenDemo", website = "chenDemo.com", revision = 2)
    public void demo() {
        System.out.println("I am demo method");
    }
}

(4)测试

public class AnnotationParser {
    public final static String TAG = "AnnotationParser"; 
    /**
     * 解析注解
     */
    public static void parser()  {
        String clazz = "com.read.kotlinlib.annotation.AnnotationDemo";
        Method[] demoMethod = new Method[0];
        try {
            demoMethod = AnnotationParser.class.getClassLoader().loadClass(clazz).getMethods();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        for (Method method : demoMethod) {
            if (method.isAnnotationPresent(AuthorAnno.class)) {
                AuthorAnno authorInfo = method.getAnnotation(AuthorAnno.class);
                System.out.println(TAG + " method: " + method);
                System.out.println(TAG + " name= " + authorInfo.name() + " , website= " + authorInfo.website() 
                		+ " , revision= " + authorInfo.revision());
            }
        }
//      AnnotationParser ethod: public void com.read.kotlinlib.annotation.AnnotationDemo.demo()
//      AnnotationParser ame= chenDemo , website= chenDemo.com , revision= 2
//      AnnotationParser ethod: public static void com.read.kotlinlib.annotation.AnnotationDemo.main()
//      AnnotationParser ame= chen , website= chen.com , revision= 1
    }
}

(5)通过反射技术来解析自定义注解@AuthorAnno,关于反射类位于包java.lang.reflect,其中有一个接口AnnotatedElement,该接口定义了注释相关的几个核心方法,如下:
在这里插入图片描述
(6)通过getAnnotation获取注解的信息
在这里插入图片描述

5 参考链接

Java元注解详解及使用与自定义注解

Java注解(Annotation)

基础篇:带你从头到尾玩转注解

18 注解是什么?有哪些元注解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值