我们在实际项目开发中使用注解的目的:为了追求低耦合,降低后期的维护成本。平时自己在实际项目也会经常用到注解,所以想着自己总结一下注解的知识点,毕竟"好记性不如烂笔头",忘记的时候可以自己打开文章梳理一下,下面开始进入正文。
一、注解的本质:
从源码角度分析
[java.lang.annotation.Annotation」接口中有这么一句话,用来描述『注解』。 The common interface extended by all annotation types. 翻译成中文:所有的注解类型都继承自这个普通的接口(Annotation)
举个简单的例子理解一下,平时开发中我们使用最多的就是@Overrider注解,实际上该注解的基类是annotation接口;
//用代码描述即如下: public interface Override extends Annotation{ }
关于上述代码,有感兴趣的伙伴们可以找到内置java注解,反编译验证。
因此,java注解中不能支持继承其他类或接口。
二、注解:
2.1注解解析
解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。
编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。
典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。这一种情况只适用于那些编译器已经熟知的注解类,比如 JDK 内置的几个注解,而你自定义的注解,编译器是不知道你这个注解的作用的,当然也不知道该如何处理,往往只是会根据该注解的作用范围来选择是否编译进字节码文件,仅此而已。
2.2注解支持的数据类型
-
所有基本类型(int,float,boolean,byte,double,char,long,short)
-
String
-
Class
-
enum
-
Annotation
-
上述类型的数组
倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型。
三、元注解:
元注解的定义:可以理解为用于修饰注解的注解,通常用于注解的定义上。例如,从源码举例:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
其中@Target、@Retention便是元注解,主要用于定义注解的生命周期和作用目标等信息;
下面主要介绍一下元注解:
3.1@Target
@Target:注解的作用目标:用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。
源码定义:
用法:
@Target(value = {ElementType.FIELD})
从源码中可以看到target注解中存在一个ElementType类型的value,那么我们进入这个枚举里面看下:
public enum ElementType { /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/ TYPE, /** 标明该注解可以用于字段(域)声明,包括enum实例 */ FIELD, /** 标明该注解可以用于方法声明 */ METHOD, /** 标明该注解可以用于参数声明 */ PARAMETER, /** 标明注解可以用于构造函数声明 */ CONSTRUCTOR, /** 标明注解可以用于局部变量声明 */ LOCAL_VARIABLE, /** 标明注解可以用于注解声明(应用于另一个注解上)*/ ANNOTATION_TYPE, /** 标明注解可以用于包声明 */ PACKAGE, /** * 标明注解可以用于类型参数声明(1.8新加入) * @since 1.8 */ TYPE_PARAMETER, /** * 类型使用声明(1.8新加入) * @since 1.8 */ TYPE_USE }
3.2@Retention:
@Retention 用于指明当前注解的生命周期;
源码定义:
用法:
@Retention(value = RetentionPolicy.RUNTIME)
同理,该元注解依然存在一个枚举类型的value,我们跟进源码中分析:
public enum RetentionPolicy { /** * 当前注解编译期可见,不会写入 class 文件(该类型的注解信息只会保留在源码里,源码 *经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里) */ SOURCE, /** * 类加载阶段丢弃,会写入 class 文件(该类型的注解信息会保留在源码里和class文件 * 里,在执行的时候,不会加载到虚拟机中) */ CLASS, /** * 永久保存,可以反射获取(源码、class文件和执行的时候都有注解的信息) * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
四、java内置注解:
@Override:用于标明此方法覆盖了父类的方法,源码如下
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。
它就是一种典型的『标记式注解』,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。
@Deprecated:用于标明已经过时的方法或类,源码如下,关于@Documented稍后分析:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。
当然,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。
@SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
其内部有一个String数组,value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。
主要接收值如下:
deprecation:使用了不赞成使用的类或方法时的警告; unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告; path:在类路径、源文件路径等中有不存在的路径时的警告; serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告; finally:任何 finally 子句不能正常完成时的警告; all:关于以上所有情况的警告。