Annotation
Annotation
简介
Annotation
(注解)是JDK5开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类,方法或者变量,在框架中大量使用(如 Spring、Mybatis等)
注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。
下面是我简单写的一个自定义注解
|
通过对上述文件的字节码(.class)的反编译(javap - p xxx.class)可以得到@interface
其实就是一个继承了Annotation
的一个接口
javap -p Test.class |
public interface com.dyw.annotation.Test extends java.lang.annotation.Annotation { |
注解只有被解析之后才会生效,常见的解析方法有两种:
- 编译期间直接扫描:编译器在编译Java代码的时候扫描对于的注解并处理,比如某个方法使用了
@Override
,编译器在编译的时候就会检测当前的方法是否重写了父类对于的方法。 - 运行期间通过反射处理:这个经常在Spring框架中看到,例如Spring的
@Value
注解,就是通过反射来进行处理的。
注解详细介绍
我们通过上述的例子可以看到我们的注解上面还有着其他的注解例如@Retention
、@Target
(这些都统称为元注解).
所以一个注解是由以下成分组成
- 元注解
public @interface 注解名称
元注解介绍
JDK1.8版本为我们提供了6个标准的用来对注解类型进行注解的注解类(1.8之前只有四个),我们称之为meta-annotation
(元注解).
元注解只能用在注解之上(自定义注解时可用)
@Target
@Retention
@Documented
@Inherited
@Native
(1.8新增)
@Repeatable
(1.8新增)
@Target
官方解释:
指示注解类型适用的上下文。注解类型可能适用的声明上下文和类型上下文在 JLS 9.6.4.1 中指定,并在源代码中由java.lang.annotation.ElementType的枚举常量表示。 |
- 它指明了它所修饰的注解使用的范围 如果自定义的注解为含有@Target元注解修饰,那么默认可以是在(除类型参数之外的)任何项之上使用,若有@Target元注解修饰那么根据Value(ElementType枚举常量)的指定的目标进行规定。
ElementType
ElementType.class
public enum ElementType { |
- ElementType的枚举常量指明了注解可以使用的目标。
//可修饰在方法之上 |
@Retention
官方解释:
指示要保留带注解类型的注解多长时间。如果注释类型声明中不存在保留注释,则保留策略默认为RetentionPolicy.CLASS 。 |
- 即
@Retention
用来约束注解的生命周期,分别有三个值,源码级别(source)、类文件级别(class)或者运行时级别(runtime)可以通过指定@Retention
中的值来实现(值为RetentionPolicy
枚举常量)。
RetentionPolicy.class
public enum RetentionPolicy { |
注意:生命周期大小排序为SOURCE < CLASS < RUNTIME
,范围依次增大,前者能使用的地方后者一定能使用。如果需要在运行时去动态获取注解信息,那只能使用RUNTIME
;如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就是用CLASS
;如果只是做一些检查性的操作,比如@Override和@SupperssWarning
,可选择SOURCE
@Documented
官方解释:
表示默认情况下,带有类型的注释将由 javadoc 和类似工具记录。这种类型应该用于注解类型的声明,这些类型的注释会影响其客户对注释元素的使用。如果使用 Documented 对类型声明进行注释,则其注释将成为注释元素的公共 API 的一部分。 |
- 带上该注解后的注解表明,在默认情况下这个注解是由JavaDoc和类似工具记录的,即带上了该文档化的注解被使用再生成文档时,会称为API的一部分。(默认情况下JavaDoc是不包含注解的,除非声明注解的时候使用了
@Documented
)
Person.java
|
main.java
public class main { |
- 生成的文档
- 不带
@Documented
注解生成的文档
@Inherited
官方解释:
指示注解类型是自动继承的。如果注解类型声明中存在 Inherited 元注解,并且用户在类声明中查询注解类型,并且类声明没有该类型的注解,则将自动查询该类的超类以获取注解类型。将重复此过程,直到找到此类型的注释,或到达类层次结构(对象)的顶部。如果没有超类具有此类型的注释,则查询将指示所讨论的类没有此类注释。 |
- 被该元注解修饰的自定义注解再使用后会自动继承,如果使用了该自定义注解去修饰一个class那么这个注解也会作用于该class的子类。就是说如果某个类使用了被
@Inherited
修饰的注解,则其子类将会自动具有该注释
注意: @Inherited annotation
类型是被标注过的class
的子类所继承。类并不从它所实现的接口继承annotation
,方法并不从它所重载的方法继承annotation
。
//使用@Inherited修饰的自定义注解 |
main.java
public class main { |
@Native
官方解释:
表示可以从本机代码引用定义常量值的字段。注释可以被生成本机头文件的工具用作提示,以确定是否需要头文件,如果需要,它应该包含哪些声明。 |
- 使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
@Repeatable
官方解释:
注释类型java.lang.annotation.Repeatable用于指示它(元)注释其声明的注释类型是可重复的。 @Repeatable的值表示可重复注解类型的包含注解类型。 |
@Repeatable
允许在相同的程序元素中重复注解(不报错)。在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。
不使用@Repeatable
修饰的自定义注解完成重复注解
|
|
使用@Repeatable
修饰的自定义注解
|
|
两种方法不同的地方是,创建重复注解Person时加上了
@Repeatable
注解,指向存储注解Persons,这样使用时就可以直接重复使用Person注解。从上述例子可以看出使用@Repeatable
注解更符合常规思维,可读性强。但两种方法的效果相同,只是使用了
@Repeatable
注解简化了写法,这种简化的底层依旧是多个重复注解使用了一个被称作“容器”注解的value的成员的数组元素处理。
JDK基本注解介绍
基本注解包括
@Override
@Deprecated
@SuppressWarnings
@SafeVarargs
@FunctionalInterface
@Override
官方解释:
指示方法声明旨在覆盖超类型中的方法声明。如果使用此注解类型对方法进行注解,则编译器需要生成错误消息,除非至少满足以下条件之一: |
public class Father { |
- 如果将子类中方法名
msg
改为mg
会发生如下编译错误
java: 方法不会覆盖或实现超类型的方法
- 所以
@Override
的作用告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否者就会出错,这样可以帮助程序员避免一些低级错误。
@Deprecated
官方解释:
@Deprecated 注释的程序元素是不鼓励程序员使用的程序元素,通常是因为它很危险,或者因为存在更好的替代方案。当在非弃用代码中使用或覆盖弃用的程序元素时,编译器会发出警告。 |
- 通俗的说被该注解修饰的目标项是已经过时的了,不推荐使用的。**
public class Son extends Father{ |
- 使用
@Deprecated
修饰了Son中的msg
方法后,调用该方法会出现删除线和编译警告。
@SuppressWarnings
官方解释:
指示应在带注释的元素(以及带注释的元素中包含的所有程序元素)中抑制命名的编译器警告。请注意,给定元素中抑制的警告集是所有包含元素中抑制的警告的超集。例如,如果您注释一个类以抑制一个警告并注释一个方法以抑制另一个警告,则两个警告都将在方法中被抑制。 |
|
Java中的
@SuppressWarnings
注解指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。如果你对于代码的规范不做要求又对编译器的警告感到烦躁那么你可以使用
@SuppressWarnings
(仅仅只是取消显示,并没有消除),它可以让你免去这些烦恼,当然编译器报错他是无法帮你取消显示的。添加前:
- 添加后:
注解的使用有以下三种:
- 抑制单类型的警告:
@SuppressWarnings("unchecked")
- 抑制多类型的警告:
@SuppressWarnings("unchecked","rawtypes")
- 抑制所有类型的警告:
@SuppressWarnings("unchecked")
- 抑制单类型的警告:
抑制警告的关键字如下表所示:
关键字 用途 all 抑制所有警告 boxing 抑制装箱、拆箱操作时候的警告 cast 抑制映射相关的警告 dep-ann 抑制启用注释的警告 deprecation 抑制过期方法警告 fallthrough 抑制在 switch 中缺失 breaks 的警告 finally 抑制 finally 模块没有返回的警告 hiding 抑制相对于隐藏变量的局部变量的警告 incomplete-switch 忽略不完整的 switch 语句 nls 忽略非 nls 格式的字符 null 忽略对 null 的操作 rawtypes 使用 generics 时忽略没有指定相应的类型 restriction 抑制禁止使用劝阻或禁止引用的警告 serial 忽略在 serializable 类中没有声明 serialVersionUID 变量 static-access 抑制不正确的静态访问方式警告 synthetic-access 抑制子类没有按最优方法访问内部类的警告 unchecked 抑制没有进行类型检查操作的警告 unqualified-field-access 抑制没有权限访问的域的警告 unused 抑制没被使用过的代码的警告
@SafeVarargs
官方解释:
程序员断言带注释的方法或构造函数的主体不会对其 varargs 参数执行潜在的不安全操作。将此注释应用于方法或构造函数会抑制有关不可具体化的变量 arity (vararg) 类型的未经检查的警告,并抑制有关在调用站点创建参数化数组的未经检查的警告。 |
在学习@SafeVarargs
之前先来看看下面有一段代码
public class main { |
这段代码中我设计了一个接收可变参数的方法public static <T> void display(T ...array){}
可变参数方法中的参数类型相同,为此声明参数是需要指定泛型。
但是调用可变参数方法时,应该提供相同类型的参数,但是代码中传入了不同类型的参数集合,此时可以看到display签名处有如下警告
翻译过来就是参数化可变参数类型可能造成的堆污染
并且提示添加@SafeVarargs
注解(仅仅起一个取消显示的作用,某种方面上来说是和 @SuppressWarnings
作用相同的)。
这个警告是 unchecked(未检查不安全代码),就是因为将非泛型变量赋值给泛型变量所发生的。
可以发现加上了@SafeVarargs
注解后编译器警告没有显示了,你肯定会说我使用@SuppressWarnings
效果也是一样的,效果虽然一样,但是两者相较来说这里使用@SafeVarargs
注解更合适
注意:@SafeVarargs
注解不适用于非 static
或非 final
声明的方法,对于未声明为 static
或 final
的方法,如果要抑制 unchecked
警告,可以使用 @SuppressWarnings
注解。
@FunctionalInterface
官方解释:
一种信息性注解类型,用于指示接口类型声明旨在成为 Java 语言规范定义的功能接口。从概念上讲,函数式接口只有一个抽象方法。由于默认方法有一个实现,它们不是抽象的。如果接口声明了一个覆盖java.lang.Object的公共方法之一的抽象方法,这也不会计入接口的抽象方法计数,因为接口的任何实现都将具有来自java.lang.Object或其他地方的实现(接口的实现是类,所有类的父类都是Object)。 |
在学习Lambda表达式时,我们了解过函数式接口(接口中只有个一个抽象方法可以存在多个默认方法或多个static方法)。
@FunctionalInterface
作用就是用来指定某一个接口必须是函数式接口的,所以@FunctionalInterface
只能修饰接口。这里我写了两个抽象方法出现了编译器报错
- 这里我只写了一个抽象方法 一个static方法和一个默认方法 符合要求没有报错
注意:如果接口声明了一个覆盖java.lang.Object
的公共方法之一的抽象方法,这也不会计入接口的抽象方法计数
得出结论:@FunctionalInterface
只是告诉编译器去检查这个接口是不是函数式接口,保证该接口只能包含一个抽象方法,否者就会出现编译错误。
写一个自己的注解
注解的格式就是
- 元注解
public @interface 注解名
- 注解内容体
//这里我希望我的注解能够生成在JavaDoc生成的文档中 |
想要获取注解中的成员变量需要使用反射的知识、首先需要获取类的Class对象,我们就可以通过的这个Class对象反射得到注解的成员变量了。
反射相关的可以参考这篇博客反射
public class main { |
输出:
这里只介绍了自定义注解类的简单用法,但是却完美展现了注解搭配反射可以碰撞出的巨大火花。熟悉掌握注解与反射后就有一定能力去学习那些大佬开发的框架的底层代码了。
参考
java.lang.annotation (Java Platform SE 8 ) (oracle.com)
理解注解中的@Inherited - 掘金 (juejin.cn)
java元注解@Native && @Repeatable (java8 新增)_似火似水的博客-CSDN博客_native注解