使用Java注解来简化你的代码
注解(Annotation)就是一种标签,可以插入到源代码中,我们的编译器可以对他们进行逻辑判断,或者我们可以自己写一个工具方法来读取我们源代码中的注解信息,从而实现某种操作。需要申明一点,注解不会改变编译器的编译方式,也不会改变虚拟机指令执行的顺序,它更可以理解为是一种特殊的注释,本身不会起到任何作用,需要工具方法或者编译器本身读取注解的内容继而控制进行某种操作。本篇文章将从以下几点详细的介绍下Java注解的使用:
- 元数据和注解(Annotation)
- 按照参数个数分类注解(标记,单值,完整)
- 按照注解使用途径分类(标准,元注解,自定义)
- 自定义注解处理器完成读取注解内容的操作
一、元数据和注解
元数据(meta-data)就是指用来描述数据的数据,它往往是以标签的形式出现,主要用于描述代码块之间的联系。我们的注解就是一种元数据,根据它所起到的作用,我们可以大致将它分为以下三类:
- 编写文档:通过代码中标识的元数据生成文档
- 代码分析:通过代码中的元数据获取其中信息内容
- 编译检查:通过标记注解可以完成对代码块的检查,例如:@Override,用于检查格式
二、标准注解(系统自带)
在我们jdk的java.lang包中定义了三个注解,他们是:@Override,@Deprecated,@SuppressWarnnings。Override这个注解我们经常会使用到,在子类重写父类方法的时候就会使用到,他会帮助我们校验格式,确保我们正在定义的方法是在重写了父类的对应方法。Deprecated注解一般修饰在类或者方法之前,用于表示该方法或者类已经不再推荐使用了。SuppressWarnnings注解主要用于抑制编译器警告,具体的我们简单的演示下。
public class People {
public void sayHello(){
System.out.println("helo walker");
}
}
public class Student extends People {
/*@Override
public void sayHello(){
System.out.println("hello yam");
}这样是没有问题的*/
@Override
public void say(){
System.out.println("hello yam");
}/*如果你定义的方法不能重写父类某个方法,要么拼写错误,参数个数,方法名不一样等,编译抛出警告*/
}
我们需要注意的是,这里的override注解只能用于修饰方法,不能用于修饰类或者域。
public class Student extends People {
@Deprecated
public void say(){
System.out.println("hello yam");
}
}
//调用过时方法
public static void main(String[] args){
Student s = new Student();
s.say();
}
虽然编译时抛出了警告,但是程序依然可以正常的运行结束。此注解只是告知用户被标记的方法或者类已经不再推荐使用,但是你依然是可以使用的。之所以建议不再使用,一定是有了更好的取代物了,如果你一定要在你的项目中使用,等待新的jdk版本发布之后,很可能删除了这些方法或者类,可能会导致你的项目原先的一些方法或者类无法识别。
@SuppressWarnings("deprecation")
public static void main(String[] args){
Student s = new Student();
s.say();
}
例如,我们可以使用SuppressWarnings注解,阻止弹出过时警告。关于SuppressWarnings的参数主要有以下几种:
- deprecation:使用了不赞成使用的类或方法时的警告
- unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
- fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
- path:在类路径、源文件路径等中有不存在的路径时的警告;
- serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
- finally:任何 finally 子句不能正常完成时的警告;
- all:关于以上所有情况的警告。
三、元注解
元注解就是用来注解注解的注解。定义可能有点绕,其实元注解是一种注解,他可以加在一般的注解上用于限制该注解的使用范围,生命周期等。一般在自定义注解时候使用的多。在jdk的中java.lang.annotation包中定义了四个元注解:
- @Target:指定被修饰的注解的作用范围
- @Retention:指定了被修饰的注解的生命周期
- @Documented:指定了被修饰的注解是可以被例如Javadoc等工具文档化的
- @Inherited:指定了被修饰的注解修饰程序元素的时候是可以被子类继承的
我们首先看看@Target的使用:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这是系统注解Override的定义源代码,我们看到Target注解中参数ElementType.METHOD表示该注解只能用于修饰方法。使用Target注解限定了Override的修饰范围只能使方法,不能是类或者域。Target还有一些其他的参数:
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
通过上述的参数我们可以在定义一个注解的时候限定他的作用范围。
下面看看Retention这个元注解,依然以注解Override为例,
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
我们看到Retention中使用了参数RetentionPolicy.SOURCE,这个参数表示该注解只在源代码中有效,进过编译之后将会被丢弃。还有一些其他参数:
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
SOURCE表示编译器编译之后的class文件中是不存在这一行注解代码的,CLASS范围表示编译器编译之后,注解代码存在于class文件中,但是jvm在加载此class文件的时候会自动忽略掉这一行注解代码。RUNTIME表示jvm加载class文件的时候会被读取到内存,也就是运行时保留。
接着使注解Documented,这是一个关于文档的元注解,被它注解的注解在注解其他方法或者类的时候可以被Javadoc等工具文档化,对于一般的注解,在Javadoc等工具文档化类或者方法的时候会丢弃注解内容,使用它就可以使得文档化的时候依然保存着注解代码。
//Test是一个被元注解Documented修饰
public class User {
@Test(value = 10,description = "do something")
public void test1() {
}
}
使用Javadoc生成API:
类User中的方法test1方法的头部是保留着注解的,如果是一般的注解则不会保留。
最后是元注解Inherited,我们知道如果一个普通的注解修饰了一个父类,那么他的子类是不能继承修饰父类的注解的。
@Deprecated
public class People {
public void sayHello(){
System.out.println("helo walker");
}
}
public class Student extends People {
public void say(){
System.out.println("hello yam");
}
}
我们可以看到,在父类people上使用了注解Deprecated,people类名上是有删除线的(粘贴到此处并没有显示)表示此类不推荐使用,但是我们可以看到在子类Student上是没有删除线的,也就是父类废弃了,子类依然是正常的。(注解不会被继承),但是如果我们希望子类能够继承父类的某些注解,那么只需要在定义该注解的时候使用我们的元注解Inherited修饰即可。
四、自定义注解
以上我们看到的标准注解,元注解都是jdk中定义好了的,如果我们想要自定义一个自己的注解就需要通过@interface来定义一个全新的注解。
//定义一个注解
public @interface myAnnotion {
}
使用@interface定义一个注解的时候,会自动继承java.lang.annotation.Annotation接口,以下是其中的一些方法:
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
我们自定义的注解,除了多了个@符号,其他的和定义一个接口是一样的,所以这些方法我们不用实现。以上我们定义的是一个没有注解体的一个注解,像这样的注解我们叫做标记注解,这是表示一种标记,编译器根据某个类或方法是否具有此标记来判断是否要添加一些代码或做一定的检测操作。例如:@Override注解就是一个标记注解,如果某个方法前被修饰了此注解,编译器在编译时会找到父类,判断对应的方法是否完成了重写的格式。
下面声明了一个具有注解体的注解:
public @interface myAnnotion {
String name() default "";
int age();
}
我们说过,声明注解和声明接口很是类似,所以注解中的所有参数都必须以抽象方法的形式存在,例如上面一样。接下来我们看如何使用该注解:
@myAnnotion(name = "walker",age=10)
public class Test_ann {
public static void main(String[] args){
}
}
之前我们说过,注解本身不会起到任何作用,需要配合注解处理器才能发挥一定的作用,自己本身其实更像是一种特殊的注释。在上例中,我们可以在()中为注解的内部参数赋值,需要注意的是,注解的参数不允许为null,也就是在使用注解的时候,内部的每个参数都是必须要有数值的,要么在定义的时候给赋上默认值(使用default关键字),要么在()内显式的赋值。允许的注解参数类型有:
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
如果我们想要表示注解中某个参数不存在,该怎么办呢?比如我们用上述自定义的注解去修饰了一个People类,如果此人的age不知道,我们该如何赋值(参数的值不能为null)。我们往往用一些特殊值来标记某个参数不存在的情况,例如我们可以给age赋值-1表示此人年龄不详,在使用注解处理器读取的时候发现age等于-1,我们就知道此人年龄不详。往往字符串类型的参数用""表示参数不存在,整型类型参数使用负数表示参数不存在。
五、使用注解处理器响应注解
我们说过一个注解被定义出来之后,是不能完成任何作用的,如果没有注解处理器响应的注解和注释差不多。本小节我们看看如何定义一个注解处理器来对我们自定义的注解进行响应。还有一个前提是:我们的注解处理器实际上也是类,所以它只有在被加载到jvm中才能生效,但是如果我们的注解的生命周期范围到不了jvm的话,注解处理器也是没用的。
Java扩充了其反射机制,使得我们可以利用反射来获取注解信息。反射中的Class,Method,Constructor,Field,Package都继承了接口AnnotatedElement,这个接口主要有以下几个方法:
/*判断是否存在指定的注解*/
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
/*获取指定的注解*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
/*获取当前元素的所有注解*/
Annotation[] getAnnotations();
/*返回直接存在于此元素上的指定的注解,忽略继承,如果没有返回null*/
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
/*返回直接存在于此元素上的所有注解,忽略继承*/
Annotation[] getDeclaredAnnotations();
下面看一个注解的简单总和实例:
@Target(value={ElementType.FIELD})//修饰Filed的注解
@Retention(value = RetentionPolicy.RUNTIME ) //运行时保留
public @interface PName {
String name() default "";
}
@Target(value={ElementType.FIELD})//修饰Filed的注解
@Retention(value = RetentionPolicy.RUNTIME ) //运行时保留
public @interface PAge {
int age() default 0;
}
@Target(value={ElementType.METHOD})//修饰method的注解
@Retention(value = RetentionPolicy.RUNTIME ) //运行时保留
public @interface SayHello {
String content() default "hello";
}
public class People {
@PName(name = "people")
private String name;
@PAge(age = 20)
private int age;
@SayHello(content = "hello people")
public void sayHello(){
System.out.println("hello people");
}
}
public static void main(String[] args) throws NoSuchMethodException {
//获取people类中所有注解信息
Field[] fields = People.class.getDeclaredFields();
for(Field f : fields){
//遍历每个属性
if(f.isAnnotationPresent(PName.class)){
PName pn = f.getAnnotation(PName.class);
System.out.println(pn.name());
}else{
PAge pa = f.getAnnotation(PAge.class);
System.out.println(pa.age());
}
}
Method md = People.class.getMethod("sayHello");
SayHello sh = md.getAnnotation(SayHello.class);
System.out.println(sh.content());
}
上述的代码完成了将people类中所有注解信息全部获取打印的工作。这个例子可能不能准确的描述注解在我们程序中的作用(起码注解不会用来干这个),但是在一方面演示了定义到使用注解的过程,希望对大家在项目中实际使用有所启发。
最后,本篇文章结束了,望大家多多留言交流,相互学习。
使用Java注解来简化你的代码的更多相关文章
- 使用Spring注解来简化ssh框架的代码编写
目的:主要是通过使用Spring注解的方式来简化ssh框架的代码编写. 首先:我们浏览一下原始的applicationContext.xml文件中的部分配置. <bean id="m ...
- paip.java 注解的详细使用代码
paip.java 注解的详细使用代码 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn.net/att ...
- Java注解实践
Java注解实践 标签 : Java基础 注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以通过工具对被注解的代码进行特殊处理. JDK ...
- Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)
注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理.在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译. Java5中提供了apt工具来进行编译期的注解处 ...
- Java注解总结2
注解是Java元数据,可以理解成代码的标签,正确使用能极大的简化代码的编写逻辑,在各种框架代码中使用也越来越多. 一.注解的应用场景 生成doc文档: 编译器类型格式检查: 运行时处理如注入依赖等 二 ...
- 秒懂,Java 注解 (Annotation)你可以这样学 - CSDN博客
https://blog.csdn.net/briblue/article/details/73824058 文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我个人比较 ...
- 秒懂,Java 注解 (Annotation)你可以这样学
转自: https://blog.csdn.net/briblue/article/details/73824058 文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我 ...
- Java 注解(Annotation)秒懂,你可以这样学,
文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我个人比较尊敬老罗的.至于为什么放这张图,自然是为本篇博文服务,接下来我自会说明.好了,可以开始今天的博文了. Anno ...
- (转)秒懂,Java 注解 (Annotation)你可以这样学
转自:秒懂,Java 注解 (Annotation)你可以这样学 注解如同标签 回到博文开始的地方,之前某新闻客户端的评论有盖楼的习惯,于是 “乔布斯重新定义了手机.罗永浩重新定义了傻X” 就经常极为 ...
随机推荐
- Unbutu14.04 切换ROOT用户后无法启用音频
系统环境: Ubuntu14.04 x64 问题描述: 今天安装了Ubuntu14.04的64位系统,启用root用户登录后,观看视频时出现没有声音的现象. 问题原因: Ubuntu安装后默认root ...
- unity3d教程-01-安装及使用Unity
我们前往unity官网:https://unity3d.com/cn/ 选择下载个人版,免费使用,功能齐全,就是在应用启动时有unity的动画 支持正版从我做起 整个安装过程需要网络的支持 下载安装程 ...
- Struts2学习笔记⑦
今天我宛若一个智障- Struts2学的差不多了,今天开始做数据库CURD操作的案例,发现模型驱动一直报NullPointerException异常-.我的妈,我查了半天觉得没啥问题,把关注点放在了g ...
- Java线程池ExecutorService
开篇前,我们先来看看不使用线程池的情况: new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override ...
- java爬虫查找四川大学所有学院的网站的网址中的通知和新闻——以计算机学院为例
需求:查找四川大学所有学院的网站的网址中的通知和新闻——以计算机学院为例 流程图 3. 具体步骤 (1) 学院的主页为:http://cs.scu.edu.cn/ 获取该页面的所有内容(本文只获取新闻 ...
- ORM-Dapper学习<二>
Dapper的简介 Dapper是.NET下一个micro的ORM,它和Entity Framework或Nhibnate不同,属于轻量级的,并且是半自动的.Dapper只有一个代码文件,完全开源,你 ...
- 理解云计算的(IaaS PaaS SaaS)
本文不经允许,不得转载! 云计算技术已经慢慢普及了.我们做技术的有必要学习云计算技术. IaaS:Infrastructure-as-a-Service(基础设施即服务)云计算到来之前,很多企业都是自 ...
- Android使用Aspectj
使用AspectJ 集成步骤: 1.AS配置Aspectj环境 2.配置使用ajc编译 4.定义注解 5.配置规则 6.使用 7.注意事项 AS配置Aspectj环境.Aspect目前最新版本为 1. ...
- 分离数据库时出错:无法对数据库'XXX' 执行删除,因为它正用于复制"的解决方法
出现的原因是要分离的数据库是一个发布订阅的数据库.因为正在复制,所以无法脱机. 解决办法是停止发布订阅,或者删掉它..再分离.有部分情况是在复制目录下并没有看到发布订阅. 有可能是因为以前建立发布订阅 ...
- jQuery 事件——关于select选中
场景: eg:在管理一篇博文时,因博文的管理有一列叫:状态的列,该列有诸多状态,如:正常,待审核,删除等... 此时,若使用Select下拉列表进行状态选择,并在选中具体项值后,通过Ajax异步提交, ...