正好最近在公众号(BetterAndroid)发了一篇关于注解的文章,贴在这里吧,希望对题主有帮助。

一、什么是注解

我们都知道在Java代码中使用注释是为了提升代码的可读性,也就是说,注释是给人看的(对于编译器来说没有意义)。注解可以看做是注释的“强力升级版",它可以向编译器、虚拟机等解释说明一些事情(也就是说它对编译器等工具也是“可读”的)。比如我们非常熟悉的@Override注解,它的作用是告诉编译器它所注解的方法是重写的父类中的方法,这样编译器就会去检查父类是否存在这个方法,以及这个方法的签名与父类是否相同。

也就是说,注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时也能够解析注解。除了向编译器等传递一些信息,我们也可以使用注解生成代码。比如我们可以使用注解来描述我们的意图,然后让注解解析工具来解析注解,以此来生成一些”模板化“的代码。比如Hibernate、Spring等框架大量使用了注解,来避免一些重复的工作。注解是一种”被动“的信息,必须由编译器或虚拟机来“主动”解析它,它才能发挥自己的作用。

二、元注解

元注解即用来描述注解的注解,比如以下代码中我们使用“@Target”元注解来说明MethodInfo这个注解只能用于对方法进行注解:

@Target(ElementType.METHOD)

public @interface MethodInfo {

...

}

下面我们来具体介绍一下几种元注解。1. Retention

这个元注解表示一个注解会被保留到什么时候,比如以下代码表示Developer注解会被保留到运行时(也就是说在运行时依然能发挥作用):

@Retention(RetentionPolicy.RUNTIME)

public @interface Developer {

String value();

}

@Retention元注解的定义如下:

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.ANNOTATION_TYPE)

public @interface Retention {

RetentionPolicy value();

}

我们从以上代码中可以看到,定义注解使用@interface关键字,这就好比我们定义类时使用class关键字,定义接口时使用interface关键字一样,注解也是一种类型。关于@Documented和@Target的含义,下面我们会进行介绍。

我们在使用@Retention时,后面括号里的内容即表示它的取值,从以上定义我们可以看到,取值的类型为RetentionPolicy,这是一个枚举类型,它可以取以下值:SOURCE:表示在编译时这个注解会被移除,不会包含在编译后产生的class文件中;

CLASS:表示这个注解会被包含在class文件中,但在运行时会被移除;

RUNTIME:表示这个注解会被保留到运行时,在运行时可以JVM访问到,我们可以在运行时通过反射解析这个注解。

2. Documented

当一个注解被@Documented元注解所修饰时,那么无论在哪里使用这个注解,都会被Javadoc工具文档化。我们来看一下它的定义:

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.ANNOTATION_TYPE)

public @interface Documented {

}

这个元注解被@Documented修饰,表示它本身也会被文档化。@Retention元注解的值RetentionPolicy.RUNTIME表示@Documented这个注解能保留到运行时;@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented这个注解只能够用来修饰注解类型。

3. Inherited

表明被修饰的注解类型是自动继承的。如果你想让一个类和它的子类都包含某个注解,就可以使用@Inherited来修饰这个注解。也就是说,假设Parent类是Child类的父类,那么我们若用被@Inherited元注解所修饰的某个注解对Parent类进行了修饰,则相当于Child类也被该注解所修饰了。这个元注解的定义如下:

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.ANNOTATION_TYPE)

public @interface Inherited {

}

我们可以看到这个元注解类型被@Documented所注解,能够保留到运行时,只能用来修饰注解类型。

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)

@Inheritedpublic @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", ...})

也就是在注解类型名称后的括号内为每个注解方法指定返回值就可以使用这个注解。由于@SuppressWarnings只包含一个注解方法,所以我们使用时可以也简写为“@SuppressWarnings("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 函数。

实际上,javac中包含的注解处理器在编译时会自动查找所有继承自 AbstractProcessor 的类,然后调用它们的 process 方法。因此我们只要做好上面两件事,编译器就会主动去解析我们的编译时注解。现在,我们把上面定义的MethodInfo的Retention改为CLASS,我们就可以按照以下代码来解析它:

@SupportedAnnotationTypes({"com.customannotation.MethodInfo"})

public class MyProcessor extends AbstractProcessor {

@Override

public boolean process(Set extends TypeElement> anonotations, RoundEnvironment env) {

Map map=new HashMap<>();

for (TypeElement te:annotations) {

for (Element e:env.getElementsAnnotatedWith(te)){

MethodInfo mi=e.getAnnotation(MethodInfo.class);

map.put(e.getEnclosingElement().toString(),mi.author());

}

}

return false;

}

}

@SupportedAnnotationTypes注解指出了MyProcessor向要解析的注解的完整名字(全限定名称)。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) {

}

}

}

六、延伸阅读

对于以上叙述中可能存在的诸多不准确或是不清晰的地方,希望各位同学可以指正,谢谢大家:)

若您觉得这篇文章质量还行,欢迎关注微信公众号“BetterAndroid”。

简述java中的注释以及用法_怎样理解 Java 注解和运用注解编程?相关推荐

  1. java中常量final的用法_详解Java中final的用法

    本文主要介绍了Java中final的使用方法,final是java的关键字,本文就详细说明一下它的使用方法,需要的朋友可以参考下 概念 final 具有"不可改变的"的含义,可以修 ...

  2. java中线程调度遵循的原则_深入理解Java多线程核心知识:跳槽面试必备

    多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲.在平时工作中如若使用不当会出现数据错乱.执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关 ...

  3. java中demo接人_return的用法_如何理解java中return的用法?

    C语言中return用法?(请熟练者进) return是返回值,这个返回值是和函数的类型有关的,函数的类型是什么,他的返回值就是什么 比方主函数intmain() {}这里就必须有一个return,只 ...

  4. java中include标签的用法_原 ng-include用法分析以及多标签页面的简单实现方式

    在平时的项目开发中,应该会经常遇到上图所示的需求,就是在一个页面中有多个标签,被选中的标签颜色会高亮显示,切换不同标签显示相应的不同内容.如果内容代码过多则写在同一个html文件就会显得特别乱,所以这 ...

  5. java中的标识符和关键字_浅谈java中的标识符、修饰符和关键字

    合法标识符 Java语言中,对于变量,常量,函数,语句块均有名字,我们统统称之为Java标识符.标识符是用来给类.对象.方法.变量.接口和自定义数据类型命名的. 组成:Java标识符由数字,字母和下划 ...

  6. java中bjt和utc转化_如何在Java中转换UTC和本地时区

    我对Java中的时区感到好奇.我想从设备获取UTC时间(以毫秒为单位)并发送到服务器.当服务器向用户显示时间时,服务器会将其转换为本地时区.我系统中的时区是澳大利亚/悉尼(UTC + 11:00),测 ...

  7. java中如何调用自身结构_如何在Java中的自定义异常中设置我自己的消息,可以检索我的getMessage()但是没有使用构造函数,有什么办法吗?...

    我刚学习 Java中的异常处理.我想知道的不是尝试说: throw new Exception("My Message"); 和 String message=ex.getMess ...

  8. java compareto方法怎么排序的_深入理解Java中Comparable和Comparator排序

    本文有牛旦教育原创,头条首发,转载注明来源. 如何为需要的排序算法选择正确的接口?通过本文的分析讲解,我们会找到答案参考答案. 程序员经常需要将数据库中的元素排序为集合.数组或映射.在Java中,我们 ...

  9. java中修饰常量的事_浅谈java中的声明常量为什么要用static修饰

    今天定义一个类常量,想着也只有这个类可以用到,就没用static关键字修饰.结果sonar代码检查提示: Rename this field "PERSON_TYPE_USER" ...

最新文章

  1. Android移动开发之【Android实战项目】Textview各项属性(全)
  2. 某石油公司100-500人办公网络方案设计
  3. 文件上传——选择文件之后自动上传||文件上传的后端代码
  4. [leetcode] Container With Most Water
  5. Linux sqlplus权限不足,sqlplus登录用户提示权限不足
  6. Java网络编程从入门到精通(21):HTTP消息的格式
  7. System.Text.Json 中的 JsonExtensionData
  8. GitHub开源贡献榜:微软超越Facebook和Google排第一
  9. 【信号】函数kill、raise、abort、alarm
  10. php5.4 升级,centos上PHP5.3升级到PHP5.4及更高版本方法
  11. lua是编译成c语言再执行嘛,go_lua_c: 使用go编译lua脚本为字节码,通过网络传给c,通过c执行lua脚本。...
  12. CodeForces 828E DNA Evolution(树状数组)题解
  13. HEVC官方代码下载及码流分析软件使用
  14. 台式机linux连wifi,台式电脑怎么用wifi八种方法解决
  15. ArcGIS 矢量数据的合并
  16. D.背单词的小智(二分)
  17. java实现给手机发短信验证码
  18. 【论文阅读】Blur Calibration for Depth from Defocus
  19. 三大框架整合步骤(详细)
  20. 门禁上的push是什么意思_门禁的NC,NO,COM,PUSH,等一系列符号的含义请问?...

热门文章

  1. 猫哥教你写爬虫 046--协程-实践-吃什么不会胖
  2. 从技术角度分析推荐系统案例
  3. Linux之 xstart调用 x11vnc远程图形化桌面
  4. DevOps与持续交付实践
  5. Oracle事务处理—隔离级别
  6. 祝51CTO 生日快乐
  7. 禁用编译优化_Tomcat8史上最全优化实践
  8. 使用Pygame制作微信打飞机游戏PC版
  9. 加来道雄 基因编辑 纳米机器人_浙大专家把螺旋藻制成微纳机器人,可通过光合作用靶向治疗肿瘤...
  10. 计算机绘图 招聘,计算机绘图-网课答案.pdf