详解Java注解( Annotation )
注解的定义:
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释
注解的作用
- 生成文档
- 代码分析,通过代码里的元数据来对代码进行分析
- 在编译的时候进行检查
JDK自带的注解
在开发过程中,我们见到的JDK自带的注解主要有三个,分别是@Override
,@SuppressWarnings
,@Deprecated
,作用分别如下:
@Override
:- 主要用于标识所用的方法是继承自父类的方法,从而在编译的过程中可以被编译器检测到,如果某个方法使用了该注解,但是方法的签名(方法名以及参数类型)与父类不一致,则在编译的过程中,编译器会抛出错误,用于提示开发者。
- 最常见的就是
toString()
方法了,该方法继承自Object类。如果我们在重写该方法的时候,没有使用该注解,然后不小心将该方法的名字写错如下所示:
package cn.xuhuanfeng.annotation; public class TestAnnotation { public static void main(String[] args) { TestAnnotation test = new TestAnnotation(); System.out.println(test.toString()); } public String tostring() { return "Not true"; } }
输出结果为:
cn.xuhuanfeng.annotation.TestAnnotation@15db9742
,显然我们可以看到,toString()方法输出的结果显然不是我们所期待的,有时候就会很莫名其妙了(原因是重写toString()
方法的时候,不小心将S写错成了s,编译器会理解成有一个新的方法,叫tostring()
,这是正确的,所以就导致了调用的时候出现了预期之外的结果了),但是如果我们在重写toString()
方法的时候,加上@Override
,这个时候,如果还是按照上面的写法,编译器就会告诉我们The method tostring() of type TestAnnotation must override or implement a supertype method
,于是,我们很容易就能发现问题所在了,这是注解的好处之一,也是@Override
的作用,具体可以查看其源码即可。@Deprecated
:- 主要用于标识该包、方法、域、变量等已经不推荐使用了,一旦标识了该注解,则对应的方法、域等会划上删除线如下所示
@Deprecated public void test(){ System.out.println("Deprecated"); }
@SuppressWarnings
:- 主要用于压制编译器发出的警告,该注解需要提供参数,包括了
unchecked
all
等,分别对应不同的压制范围,如:
@SuppressWarnings("all") public void test01(){ }
- 主要用于压制编译器发出的警告,该注解需要提供参数,包括了
自定义注解
上面我们看到了JDK中自带的注解,虽然很有用,但是毕竟范围有限,种类也有限,实用性不是很大,于是Java开发者为我们提供了自定义的注解,极大了扩展了该功能,下面我们就详细来看下自定义注解的内容。
元注解
为了使用自定义注解,首先我们需要了解一个概念:元注解,所谓的元注解,其实就是注解的注解,也就是用来表示注解的注解,JDK中包含的元注解中比较常用的有以下几种类型: @Target
, @Retention
, @Documented
, @Inherited
,其中前面两种在实际开发过程中用得比较多,所以下面我们着重来介绍这两种:
@Target
:
- 用于表示所标识的注解的使用范围,其值可以是
ElementType.PACKAGE
,ElementType.CONSTRUCTOR
,ElementType.METHOD
,ElementType.FIELD
等,分别对应的标识对象为 包,构造器,方法,域变量,也就是说,只有包含了该范围,我们定义出来的注解才能用于对应的域,多种类型可以组合使用,只需要使用{}
包括起即可。 - 具体使用如下:
@Target({ElementType.PACKAGE,ElementType.CONSTRUCTOR ,ElementType.METHOD, ElementType.FIELD})
- 用于表示所标识的注解的使用范围,其值可以是
@Retention
:- 用于标识注解的存活周期,包括了
RetentionPolicy.RUNTIME
,RetentionPolicy.CLASS
,RetentionPolicy.SOURCE
,分别对应存活周期为运行时,字节码,源文件。
- 运行时:标识该注解存在于字节码中,并且在运行过程中会被JVM加载,可用于反射操作。
- 字节码:标识该注解存在于字节码中,但是运行时不被JVM加载,默认的形式。
- 源文件:标识该注解只存在源文件中,在编译过程会被编译器丢弃。
- 具体使用如下:
@Retention(RetentionPolicy.RUNTIME)
- 用于标识注解的存活周期,包括了
自定义注解
学习完了元注解之后,我们就可以开始手动编写自定义的注解了。
格式:
自定义注解的书写方式跟普通的Java类的书写方式接近,只是将class
关键字替换为@interface
,如下:@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PACKAGE}) public @interface MyAnnotation { }
从上面的元注解部分我们可以知道,我们定义的注解
MyAnnotation
只能用于注解包,而不能用于注解方法、域等。参数:
从前面的@SuppressWarnings
中我们可以知道,注解还可以带参数,不过在注解中的参数类型有点奇怪,如下:String value();// String为参数类型 value() 整体为变量 String[] value(); // String[] 为参数类型 value() 整体为变量
默认情况下,如果只有一个参数类型,我们将变量命名为value(),我们也可以声明多个参数
String name(); int age(); // 其他类型依此类推
在注解中的所有参数均可以指定默认的值,如下:
String name() default ""; int age() default -1;
由于在使用注解中我们无法标识错误的情况,所以一般情况下,会将默认类型指定为一个不合理值,用来处理注解时判断所使用的值是合理还是不合理。
使用:
定义完了一个我们的自定义注解之后,接下来我们来看下如何使用它。使用的方式跟JDK自带的注解的方式基本一致,指定对应的键值对,key为定义的参数名字,值为需要传入的值,如果是数组类型,则传入数组即可。@MyAnnotation(name="xuhaunfeng",age=23) public void test(){} //在MyAnnotation中多增加一个变量为 String[] parents(); @MyAnnotation(name="xuhaunfeng",age=23,parents={"AA","BB"}) public void test(){}
注解的应用
看完了上面的内容,可能你会觉得如果注解只是上面的用法,感觉上是没有任何作用的,确实,上面所介绍的内容都是注解的格式、定义等,但是没有涉及到其应用,注解配合反射,可以实现很多功能,例如:ORM的实现,框架中Annotation的应用等,不过目前我还没有学习到这些内容,所以在后期学习之后将会补上,敬请期待。
参考说明
这篇文章只是我个人学习过程中的一些笔记,不带有任何的商业目的,在学习过程中参考了很多的资料,主要参考深入理解Java:注解(Annotation)自定义注解入门 By竹子,在此对竹子表示感谢。如果本文涉及的一些内容有一些版权争议,还请与我联系。