Java 高级特性——注解

简介

Java注解(Annotation)又称 Java 标注,是JDK1.5 引入的一种注释机制。

和 Javadoc(注释)不同,Java 注解可以通过反射获取标注内容。在编译期生成类文件时,标注可以被嵌入到字节码中。 JVM 可以保留注释内容,在运行时可以获取到注释内容。当然它也支持自定义Java注释。

理解 Annotation 的关键,是理解 Annotation 的语法和用法;理解 Annotation 的语法和用法之后,再看 Annotation 的框架图,可能有更深刻的体会。

注解的分类

大致分为三类:JDK 内置注解、自定义注解、第三方框架提供的注解。

使用的位置

实际开发中,注解常常出现在类、方法、成员变量、形参位置。当然还有其他位置,这里不阐述。

级别

注解和类、接口、枚举是同一级别的。

注解的作用

如果说注释是写给人看的,那么注解就是写给程序看的。它就像一个标签,贴在一个类、方法或字段上。

它的目的是为当前读取该注解的程序提供判断依据。

  • 比如程序只要读到加了@Test 注解的方法,就知道该方法是待测试方法
  • 比如@Before注解,程序看到这个注解就知道该方法要放在@Test方法之前执行

JDK内置注解

Java 定义了一套注解,共有7个,3个在java.lang中,剩下4个在java.lang.annotation中。

  • @Override 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated 不推荐使用,标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings 警告镇压,指示编译器去忽略注解中声明的警告。

元注解

元注解的作用就是负责注解其他的注解,Java定义了4个标准的meta-annotation类型,它们被用来提供对其他annotation类型作说明。

  • @Retention 标识这个注解怎么保存,是只在代码(SOURCES)中,还是编入类文件(CLASS)中,或者是在运行时(RUNTIME)可以通过反射访问。
  • @Documented 标记这些注解是否包含在用户文档中。
  • @Target 标记这个注解应该是一名Java成员。
  • @Inherited 标记这个注解是继承于该注解类(替代注解并没有继承于任何子类)

从Java1.7开始,额外添加了3个注解

  • @SafeVarargs Java1.7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface Java1.8开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable Java1.8开始支持,标识某注解可以在同一个声明上使用多次。

通过4个元注解我们可以自定义一个注解

public class Test01{
}

// 自定义一个注解
@Target(value = {ElementType.METHOD, ElementType.TYPE})  
@Retention(value = RetentionPolicy.RUNTIME)  
@Documented
@Inherited
@interface MyAnnotation{}
  • @Target 表示我们的注解可以用在哪些地方
  • @Retention 表示注解的生命周期,RUNTIME > CLASS > SOURCES
    • @Retention(RetentionPolicy.SOURCE) 注解仅存在于源码中,在class字节码文件中不包含;
    • @Retention(RetentionPolicy.CLASS) 默认的保留策略,注解会在class字节码文件中存在,它在磁盘内,而不是内存中,虚拟机将字节码文件加载进内存后注解会消失,所以运行时无法获得;
    • @Retention(RetentionPolicy.RUNTIME) 注解会在class字节码文件中存在,在运行时可以通过反射获取到
  • @Documented 表示是否将我们的注解生成在JAVAdoc中
  • @Inherited 表示子类可以继承父类的注解

自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口。

@interface 用来声明一个注解,格式:@interface注解名{定义内容}

public class Test02{
  	
  	@MyAnnotation(name = "hory", schools = {"QH","BD"}) // 如果参数没有默认值,就必须传入值
  	public void test(){}
}

// 自定义一个注解
@Target(value = {ElementType.METHOD, ElementType.TYPE})  
@Retention(value = RetentionPolicy.RUNTIME)  
@interface MyAnnotation{
  	String name();   // name()是注解的参数;String是注解的类型
  	int age() default 23;   // 有默认值的参数,可以不在上面添加参数值
  	String[] schools();  	
}

其中属性的数据类型可以为:

  • 八大基本类型
  • String
  • 枚举
  • Class
  • 注解类型
  • 以上类型的一维数组

value属性

如果注解的属性只有一个,且名字为value(),那么在使用该注解时,可以不用指定属性名,因为默认就是给value赋值。

但是注解的属性如果有多个,无论名字是否为value(),都必须写明属性名及其值的对应关系。

数组属性

如果数组的元素只有一个,可以省略{}

注解的本质

首先说结论:

  • 注解的本质是一个接口。

  • 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口。

为了验证这个结论,我们先来自定义一个注解,然后编译成字节码文件,再然后将字节码文件进行反编译。

  1. 定义一个注解
public @interface MyAnnotation {}
  1. 编译后得到字节码文件 MyAnnotation.class
  2. 通过XJad工具反编译 MyAnnotation.class 得到
import java.lang.annotation.Annotation;
public interface MyAnnotation extends Annotation {}

可以发现,@interface 变成了 interface,而且自动继承了Annotation。

既然确实是个接口,那么自然可以在里面写方法。

public @interface MyAnnotation {
    public abstract String getValue();
}

编译成 class 文件后再反编译:

import java.lang.annotation.Annotation;
public interface MyAnnotation extends Annotation {
    public abstract String getValue();
}

虽说注解的本质是接口,但是仍然有很多怪异的地方,比如使用注解时,竟然可以给getValue赋值:

@MyAnnotation(getValue="annotation on class")
public class Student {
    public String name;
    public void study(){}
}

你见过给方法赋值的操作吗?别闹了,你脑中想到的是给方法传参。

所以在注解里,类似于 String getValue() 这种,被称为“属性”,给属性赋值显然听起来好接受多了。

基于以上差异,以后还是把注解单独归为一类,不要当成接口使用。

注解的三角关系

注解就像一个标签,是贴在程序代码上供另一个程序读取的。

所以想要使用注解,那么必须有三个步骤:① 定义注解;② 在一个类/方法等上面标注刚才定义的注解;③ 在其他程序中读取类/方法等上标注的注解。

如果只定义注解,那么没有什么意义,定义完了标注也没什么意义,只有① ② ③ 全部有的时候,才是一个完整的注解使用过程。

反射操作注解

接下来写一个程序读取注解,读取注解的思路是:

  • 直接读取:IO 读取 .java 文件 --> 处理字符串 --> 得到注解信息 (太麻烦,而且注解中如果传了很复杂的值,解析起来很麻烦)

  • 反射读取:因为注解一般标识在类、方法、成员变量上,所以 反射得到Class、Method、Field —> 得到注解

所以看一下用反射是怎么操作注解的。。。

  1. 定义一个注解
  • 注意:想要用反射操作注解,就要定义注解的保留策略@Retention(value = RetentionPolicy.RUNTIME)
  • 如果没有设置保留策略,则其默认值为 RetentionPolicy.CLASS,会报 java.lang.NullPointerException,用鼠标点击下面的 MyAnnotation.class时,IDE会提示 Annotation ‘MyAnnotation.class’ is not retained for reflective,直译的话就是:注解MyAnnotation并没有为反射保留。
@Target(value={ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)   
public @interface MyAnnotation {
    String name();
    int age() default 23;
}
  1. 将注解表示在 Dog 类及其 sleep()方法上
@MyAnnotation(name = "hory")
public class Dog {

    @MyAnnotation(name = "my")
    public void sleep(){ }
}
  1. 反射获取注解信息
import java.lang.reflect.Method;

public class TestAnnotation {
    public static void main(String[] args) throws Exception {
        // 获取类上的注解
        Class<Dog> clazz = Dog.class;
        MyAnnotation annotationOnClass = clazz.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnClass.name());
        System.out.println(annotationOnClass.age());

        // 获取sleep方法s上的注解
        Method helloMethod = clazz.getMethod("sleep",null);
        MyAnnotation annotationOnMethod = helloMethod.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnMethod.name());
        System.out.println(annotationOnMethod.age());
    }
}

结果:

hory
23
my
23

总结

  • 注解就像标签,是程序判断执行的依据。比如,程序读到@Test就知道这个方法是待测试方法,而@Before的方法要在测试方法之前执行。
  • 注解需要三要素:定义、使用、读取并执行。
  • 注解分为自定义注解、JDK内置注解和第三方注解(框架)。自定义注解一般要我们自己定义、使用、并写程序读取,而JDK内置注解和第三方注解我们只要使用,定义和读取都交给它们。
  • 大多数情况下,三角关系中我们只负责使用注解,无需定义和执行,框架会将注解类和读取注解的程序隐藏起来,除非阅读源码,否则根本看不到。平时看不到定义和读取的过程,光顾着使用注解,久而久之很多人就忘了注解如何起作用了!
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值