使用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” 就经常极为 ...
随机推荐
- for循环与foreach的区别
for循环与foreach的区别 foreach 依赖 IEnumerable. 第一次 var a in GetList() 时 调用 GetEnumerator 返回第一个对象 并 赋给a, 以后 ...
- 赵本山 教你如何在实战项目中使用WCF
我们都知道调用WCF直接在Service References中引用可以远程调用的WCF Url就行了. 但是我们想过没,在Development环境中可以这样做,但是QA.UAT.Productio ...
- Modbus软件开发实战指南 之 开发自己的Modbus Poll工具 - 2
接上一篇文章的内容. 看了前面需求提到的复杂的命令行解析功能,很多人立马开始发怵,其实大可不必. 我们都知道,Linux下的程序往往都提供了复杂的命令行参数处理机制,因为这是与 其他程序或用户进行交互 ...
- TuSDK 简易使用方法 持有图片对象方式
TuSDK 为涂图照相应用的SDK,打包后文件大小约为5M,缺点为包比较大,且图片清晰度较差一些,优点为直接可以引用滤镜贴纸,方便易用. 使用方法如下: 1.AppDelegate.m 中加 ...
- 大数据系列之Flume--几种不同的Sources
1.flume概念 flume是分布式的,可靠的,高可用的,用于对不同来源的大量的日志数据进行有效收集.聚集和移动,并以集中式的数据存储的系统. flume目前是apache的一个顶级项目. flum ...
- 1635: [Usaco2007 Jan]Tallest Cow 最高的牛
1635: [Usaco2007 Jan]Tallest Cow 最高的牛 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 383 Solved: 211 ...
- SEO-发信息注意的问题
SEO是什么? SEO的全称是Search Engine Optimization,翻译过来就是搜索引擎优化,说到搜索引擎,可能不是特别的清楚.通俗点讲,就是百度,谷歌,雅虎,这些可以直接搜索到你想要 ...
- 不可重入定时器Newlife.TimerX
在.net常用的定时器类有下面三种,使用定时器时需要设定参数,如间断时间.定时器计溢出后的回调函数.延时.开始等,定时器的的主要方法有开始.终止等,不同的定时器实现上述的方法会有一些差异,本文会针对具 ...
- SignalR指定用户推送消息
一.首先,在MVC项目中安装SingalR包(SingalR2.0需要.net4.5以上,VS2010可以安装1.1.3版本,本例为VS2010+SignalR1.1.3). 打开工具-NuGet程序 ...
- JavaScript重新介绍
本文转载自 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/A_re-introduction_to_JavaScript 引言 为什么 ...