Java核心技术点之注解
本博文是对Java中注解相关知识点的简单总结,若有叙述不清晰或是不准确的地方,希望大家可以指正,谢谢大家:)
一、什么是注解
我们大家都知道Java代码中使用注释是为了向以后阅读这份代码的人解释说明一些事情,注解是注释的升级版,它可以向编译器、虚拟机等解释说明一些事情。比如我们非常熟悉的@Override就是一种元注解,它的作用是告诉编译器它所注解的方法是重写父类的方法,这样编译器就会去检查父类是否存在这个方法,以及这个方法的签名与父类是否相同。
也就是说,注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时也能够解析注解。我们在Java源文件中使用注释,是为了以后我们或他人再来读这段代码时,能够更好地理解它。Javadoc工具可以解析我们在源代码中为类、方法、变量等添加的描述信息,并根据这些描述信息自动生成一个HTML文档,这些自动生成的文档即可作为API帮助文档。只要我们为类、方法等添加的描述信息符合Javadoc要求的语法,我们就能够使用Javadoc工具根据我们的描述信息自动生成一个帮助文档。而注解比java注释和Javadoc要强大得多,它们三者之间的重大的区别在于,Java注释和Javadoc描述所发挥的作用仅仅到编译时就止步了,而注解直到运行时都能够发挥作用。
我们知道,使用“transient”关键字可以告诉编译器这个域不可序列化。相比于用”transient“这样的关键字修饰一个属性,注解为我们提供了为类/方法/属性/变量添加描述信息的更通用的方式,而这些描述信息对于开发者、自动化工具、Java编译器和Java运行时来说都是有意义的,也就是说他们都能“读懂”注解信息。”transient“关键字是一个修饰符,而注解也是一种修饰符。除了传递信息,我们也可以使用注解生成代码。我们可以使用注解,然后让注解解析工具来解析它们,以此来生成一些”模板化“的代码。比如Hibernate、Spring、Axis这些框架大量使用了注解,来避免一些重复的工作。
二、元注解
元注解即用来描述注解的注解,比如以下代码中我们使用“@Target”元注解来说明MethodInfo这个注解只能应用于对方法进行注解:
@Target(ElementType.METHOD)
public @interface MethodInfo {
...
}
下面我们来具体介绍一下几种元注解。
1. Documented
当一个注解类型被@Documented元注解所描述时,那么无论在哪里使用这个注解,都会被Javadoc工具文档化。我们来看一下它的定义:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
我们从以上代码中可以看到,定义注解使用@interface关键字,这就好比我们定义类时使用class关键字,定义接口时使用interface关键字一样,注解也是一种类型。这个元注解被@Documented修饰,表示它本身也会被文档化。@Retention元注解的值RetentionPolicy.RUNTIME表示@Documented这个注解能保留到运行时;@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented这个注解只能够用来描述注解类型。
2. Inherited
表明被修饰的注解类型是自动继承的。具体解释如下:若一个注解类型被Inherited元注解所修饰,则当用户在一个类声明中查询该注解类型时,若发现这个类声明中不包含这个注解类型,则会自动在这个类的父类中查询相应的注解类型,这个过程会被重复,直到该注解类型被找到或是查找完了Object类还未找到。这个元注解的定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
我们可以看到这个元注解类型被@Documented所注解,能够保留到运行时,只能用来描述注解类型。
3. Retention
我们在上面已经见到个这个元注解,它表示一个注解类型会被保留到什么时候,比如以下代码表示Developer注解会被保留到运行时:
@Retention(RetentionPolicy.RUNTIME)
public @interface Developer {
String value();
}
@Retention元注解的定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
我们在使用@Retention时,后面括号里的内容即表示他的取值,从以上定义我们可以看到,取值的类型为RetentionPolicy,这是一个枚举类型,它可以取以下值:
- SOURCE:表示在编译时这个注解会被移除,不会包含在编译后产生的class文件中;
- CLASS:表示这个注解会被包含在class文件中,但在运行时会被移除;
- RUNTIME:表示这个注解会被保留到运行时,在运行时可以JVM访问到,我们可以在运行时通过反射解析这个注解。
4. Target
这个元注解说明了被修饰的注解的应用范围,也就是被修饰的注解可以用来注解哪些程序元素,它的定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
从以上定义我们可以看到它也会保留到运行时,而且它的取值是为ElementType[]类型(一个数组,意思是可以指定多个值),ElementType是一个枚举类型,它可以取以下值:
- TYPE:表示可以用来注解类、接口、注解类型或枚举类型;
- PACKAGE:可以用来注解包;
- PARAMETER:可以用来注解参数;
- ANNOTATION_TYPE:可以用来注解 注解类型;
- METHOD:可以用来注解方法;
- FIELD:可以用来注解属性(包括枚举常量);
- CONSTRUCTOR:可以用来注解构造器;
- LOCAL_VARIABLE:可用来注解局部变量。
三、常见内建注解
Java本身内建了一些注解,下面我们来介绍一下我们在日常开发中比较常见的注解:@Override、@Deprecated、@SuppressWarnings。相信我们大家或多或少都使用过这三个注解,下面我们一起再重新认识一下它们。
1. @Override注解
我们先来看一下这个注解类型的定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
从它的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。这个注解的作用我们大家都不陌生,那就是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。
2. @Deprecated
这个注解的定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。
3. @SuppressWarnings
这个注解我们也比较常用到,先来看下它的定义:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:
- deprecation:忽略使用了废弃的类或方法时的警告;
- unchecked:执行了未检查的转换;
- fallthrough:swich语句款中case忘加break从而直接“落入”下一个case;
- path:类路径或原文件路径等不存在;
- serial:可序列化的类缺少serialVersionUID;
- finally:存在不能正常执行的finally子句;
- all:以上所有情况产生的警告均忽略。
这个注解的使用示例如下:
@SuppressWarning(value={"deprecation", "unchecked"})
public void myMethos() {...}
通过使用以上注解,我们告诉编译器忽略myMethod方法中由于使用了废弃的类或方法或是做了未检查的转换而产生的警告。
四、自定义注解
我们可以创建我们自己的注解类型并使用它。请看下面的示例:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {
String author() default "absfree";
String date();
int version() default 1;
}
在自定义注解时,有以下几点需要我们了解:
- 注解类型是通过”@interface“关键字定义的;
- 在”注解体“中,所有的方法均没有方法体且只允许public和abstract这两种修饰符号(不加修饰符缺省为public),注解方法不允许有throws子句;
- 注解方法的返回值只能为以下几种:原始数据类型), String, Class, 枚举类型, 注解和它们的一维数组,可以为方法指定默认返回值。
我们再把上面提到过的@SuppressWarnings这个注解类型的定义拿出来看一下,这个注解类型是系统为我们定义好的,它的定义如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
我们可以看到,它只定义了一个注解方法value(),它的返回值类型为String[],没有指定默认返回值。我们使用@SuppressWarnings这个注解所用的语法如下:
@SuppressWarnings(value={"value1", "value2", ...})
也就是在注解类型名称后的括号内为每个注解方法指定返回值就可以使用这个注解。下面我们来看看怎么使用我们自定义的注解类型@MethodInfo:
public class AnnotationTest {
@MethodInfo(author="absfree", date="20160410")
public static void main(String[] args) {
System.out.println("Using custom annotation...");
}
}
那么现在问题来了,我们使用的自定义注解对于编译器或是虚拟机来说是有意义的吗(编译器或是虚拟机能读懂吗)?显然我们什么都不做的话,编译器或者虚拟机是读不懂我们的自定义注解的。下面我们来介绍以下注解的解析,让编译器或虚拟机能够读懂我们的自定义注解。
五、注解的解析
1. 编译时解析
编译时注解指的是@Retention的值为CLASS的注解,对于这类注解的解析,我们只需做以下两件事:
- 自定义类继承 AbstractProcessor类;
- 重写其中的 process 函数。
实际上,编译器在编译时会自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法。因此我们只要做好上面两件事,编译器就会主动去解析我们的编译时注解。现在,我们把上面定义的MethodInfo的Retention改为CLASS,我们就可以按照以下代码来解析它:
@SupportedAnnotationTypes({ "com.custom.customannotation.MethodInfo" })
public class MethodInfoProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
HashMap<String, String> map = new HashMap<String, String>();
for (TypeElement typeElement : annotations) {
for (Element element : env.getElementsAnnotatedWith(typeElement)) {
MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);
map.put(element.getEnclosingElement().toString(), methodInfo.author());
}
}
return false;
}
}
@SupportedAnnotationTypes注解描述了Processor要解析的注解的名字。process 函数的annotations参数表示 表示待处理的注解集,env表示当前或是之前的运行环境。process函数的返回值表示annotations中的注解是否被这个Processor接受。
2. 运行时注解解析
首先我们把MethodInfo注解类型中Retention的值改回原来的RUNTIME,接下来我们介绍如何通过反射机制在运行时解析我们的自定义注解类型。
java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的几个方法:
T getAnnotation(Class annotationClass) //返回该程序元素的指定类型的注解,若不存在这个类型的注解则返回null
Annotation[] getAnnotations() //返回修饰该程序元素的所有注解
Annotation[] getDeclaredAnnotations() //返回直接修饰该元素的所有注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) //当该程序元素被指定类型注解修饰时,返回true,否则返回false
解析我们上面的自定义注解MethodInfo的相关示例代码如下(AnnotationParser.java):
public class AnnotationParser {
public static void main(String[] args) {
try {
Class cls = AnnotationTest.class;
for (Method method : cls.getMethods()) {
MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);
if (methodInfo != null) {
System.out.println("method name:" + method.getName());
System.out.println("method author:" + methodInfo.author());
System.out.println("method date:" + methodInfo.date());
System.out.println("method version:" + methodInfo.version());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行以上代码我们可以得到以下输出:
这说明我们已经成功解析了自定义注解。关于注解有点我们需要明确的是,作为描述代码本身的一种元数据,注解是一种”被动“的信息。也就是说,必须由编译器或虚拟机来“主动”解析它,它才能发挥自己的作用。
六、参考资料
1. Java Documention
2. 公共技术点之Java注解
3. Java 注解
Java核心技术点之注解的更多相关文章
- 《Java核心技术 卷II 高级特性(原书第9版)》
<Java核心技术 卷II 高级特性(原书第9版)> 基本信息 原书名:Core Java Volume II—Advanced Features(Ninth Edition) 作者: ( ...
- java 核心技术 读后总结
总结 1.少用八进制,以及二进制. 那么就是直接用16进制或10进制吗?额,想当年有这样搞过,后面就uuid了. 2.>>>用0填充高位>>用符号位填充高位<< ...
- 2019 最新 Java 核心技术教程,都在这了!
Java技术栈 www.javastack.cn 优秀的Java技术公众号 以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期更新. Java 基础篇 ...
- Java核心技术点之泛型
1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过了一阵,我们想要实现一个大小 ...
- Java核心技术点之集合框架
1. 概述 Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容 ...
- Java核心技术点之内部类
1. 为什么要使用内部类 内部类就是定义在一个类内部的类,那么为什么要使用内部类呢?主要原因有以下几点:第一,内部类中定义的方法能访问到它所在外部类的私有属性及方法:第二,外部类无法实现对同一 ...
- Java核心技术点之动态代理
本篇博文会从代理的概念出发,介绍Java中动态代理技术的使用,并进一步探索它的实现原理.由于个人水平有限,叙述中难免出现不清晰或是不准确的地方,希望大家可以指正,谢谢大家:) 一.概述 1. 什么是代 ...
- Java基础笔记 – Annotation注解的介绍和使用 自定义注解
Java基础笔记 – Annotation注解的介绍和使用 自定义注解 本文由arthinking发表于5年前 | Java基础 | 评论数 7 | 被围观 25,969 views+ 1.Anno ...
- Java学习记录-注解
注解 一.org.springframework.web.bind.annotation ControllerAdviceCookieValue : 可以把Request header中关于cooki ...
随机推荐
- iOS网络监测方法
方法一(官方): Reachability ============================================================================== ...
- mac PHP配置
apache默认路径配置方法 apache的配置 apache已经自带了,只需如下三个命令就可以了. 开启apache服务 sudo apachectl start 停止apache服务 sudo ...
- js 事件处理程序 事件对象
事件:用户或浏览器自身执行的动作: 事件处理程序:响应某个事件的函数: 事件流:从页面中接收事件的顺序. 1.DOM事件流 "DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段 ...
- 源码编译安装gcc-5.3.0
系统环境:Amazon Linux AMI 2015.09.2 (HVM)---Fedora 23 Server 1.下载gcc-5.3.0安装包并将gcc-5.3.0.tar.gz放到/opt目录下 ...
- MongoDB-安装&启动
MongoDB安装 使用的版本为3.0,虚拟机操作系统为rhel-server-7.0-x86_641. 将mongodb-linux-x86_64-rhel70-3.0.11.tgz上传并解压到 / ...
- 基于Tomcat + JNDI + ActiveMQ实现JMS的点对点消息传送
前言 写了一个简单的JMS例子,之所以使用JNDI 是出于通用性考虑,该例子使用JMS规范提供的通用接口,没有使用具体JMS提供者的接口,这样可以保证我们编写的程序适用于任何一种JMS实现(Activ ...
- OEM代工厂产品经理个人经历谈
创业不是一件随随便便的事情! 到2007年时,我已经在上海.广州.东莞三地的工厂打工有十来年了.正是这个时间结点,我也即将做父亲了.打了很久的工后,就开始感到疲倦,做来做去,都是给老板做,也就在这时开 ...
- 一些性能查询的SQL 备忘
--检查数据库的等待事件 from v$session_waitwhere event not like 'SQL%' and event not like 'rdbms%' --找出系统中耗时的操作 ...
- Innodb行锁源码学习(一)
Innodb是mysql数据库中目前最流行的存储引擎,innodb相对其它存储引擎一个很大的特点是支持事务,并且支持行粒度的锁.今天我重点跟大家分享下innodb行锁实现的基础知识.由于篇幅比较大,文 ...
- RabbitMQ入门教程——安装及配置
RabbitMQ是一个消息代理,一个消息系统的媒介,提供了一个通用的消息发送及接收平台,并且能够保障消息传输过程中的安全.使用erlang语言开发,开源,在易用性.扩展性.高可用性等方面表现不俗 技术 ...