看了郭神公众号推荐的注解知识,想到自己这方面有欠缺,大家一起来学习.本文主要是对自己学到的知识进行总结,和大家分享自己学到的东西.这里给出我看的文章地址一小时搞明白自定义注解.我读完了脑袋还是蒙的,不是作者写的不好,而是读完了脑子蒙蒙的没有啥印象..所以我百度看了一下相关知识,看了疯狂Java的注解(第14章),结合平常见到的一些注解(主要是Android上用的多的),写了这篇总结.让我可以更好的理解注解.
一.凡事先问为什么---为什么要有注解,注解又是什么东西?###
主要是概念性的东西也要了解.面试要用啊.
Annotation(注解)是什么?####
从JDK5也就是Java5.0开始,Java增加了对元数据(MetaData)的支持,也就是Annotation注解.这里所介绍的Annotation其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并进行相应的处理.Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注解里的元数据.
这是通用解释.看完了也只知道这个是用来干嘛的,还是一知半解,往下继续.
为什么要有注解?####
通过使用注解,程序开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息.代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署.Annotation能被用来为程序元素(类、方法、成员变量)设置元数据,但是并不影响代码的执行,无论增加、删除Annotation,代码都始终如一的执行,如果希望让程序中的Annotation在运行时起到一定的作用需要通过某种配套工具(统称APT)对Annotation的信息进行访问和处理.
二.知道是Annotation(注解)是什么,然后就要知道这个东西本身有什么.###
JDK中的元Annotation(注解).元注解的作用就是负责注解其他注解.位于java.lang.annotation包下.
基本的Annotation(注解)有5个.分别是@Retention,@Target,@Documented,@Inherited,@Repeatable(这个专门用于定义Java8新增的重复注解,不常用就先不说了,需要的可自行查询).
@Retention####
用于指定被修饰的Annotation(注解)可以保留多长时间.@Retention包含一个RetentionPolicy类型的value成员变量,所以使用的时候必须为该value成员变量指定固定的值
1.@Retention(RetentionPolicy.Class)
即class保留.也是默认值. 编译器将Annotation记录在class中.当运行Java程序时,JVM不可获取Annotation的信息.
查了网上的信息和解释,没人说这东西有什么用
2.@Retention(RetentionPolicy.SOURCE)
即源代码保留.编译器直接丢弃这种Annotation.那这种有什么用.查了下可以用来提示IDE.这东西在android开发中经常看到,可能很多人都没有关注.看下面代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override{
}
可能有的人就问了,你这都是啥啥啥.这东西可是非常的常见.再看
代码
<pre>
@Override
protected voidonStart(){
super.onStart();
}
</pre>
发现没有.这个点进去就是. @Retention(RetentionPolicy.SOURCE).我觉得一般这种既然是放在源代码中的,我们暂时不需要去关心,只需要了解即可.现在是水平研究这个没什么用,比较费劲.
3.@Retention(RetentionPolicy.RUNTIME)
即运行时保留.也是网上现阶段认为比较有用的东西.主要是用来干嘛的呢.编译器同样会把Annotation记录在class文件中,当运行Java程序时,JVM也可以获取Annotation信息,程序可以通过反射获取该Annotation信息.一般都是用在通过反射获取注解信息这一用途.具体的不是这篇文章追究的,但是大家有兴趣和精力可以去了解一下,比较是这个属性里最有用的东西.
PS:不是单指这一个元注解.注解指定value值的形式一般是value=变量值的形式.但是如果使用注解时只需要为value成员变量指定值,则使用该注解时可以直接在该注解后的括号里指定value成员变量的值.
@Target####
这被用于指定被修饰的Annotation能用于修饰哪些程序单元.就是指定注解能放在什么地方.@Target是元注解,只能修饰注解,不能直接修饰在方法、类等上.这点要注意.同理介绍的这四个都是如此.要理解这点.
这里可以看上面代码有一个@Target 可以看到@Override只能放在方法上.因为@Target里的值为METNOD.
以下为@Target的所可选的值(前缀统一为ElementType.)
1.CONSTRUCTOR: 用于描述构造器
FIELD:用于描述域.这里比较模糊.查了书上的说明是指定Annotation只能修饰成员变量.
LOCAL_VARIABLE:用于描述局部变量
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述参数
TYPE:用于描述类、接口(包括注解类型) 或enum(枚举)声明
ANNOTATION: 指定该策略的Annotation只能修饰Annotation.也就是成了一个元注解.
@Documened####
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。.我在android开发中碰见过 但是不得其解.
@Inherited
@Inherited元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
看到这里我也是一阵糊涂,这到底是啥意思.看下面的一个解释示例就大概懂了.
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME时,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
PS:这个我也没用过,因为上面的解释涉及到反射.我反射基础不行,还得回头补充.这里就不随便去写误导大家.有基础好的可以查阅资料看看.等我反射和反射注解理解了,可能会在另一篇里对这个进行试验.为什么还要写出来呢.我从我的水平和理解出发,展示我学习Annotation时碰到的问题和我的理解,也是我也这篇文章的原因.
三.了解一些基本的Annotation###
这个就不是元注解了.位于java.lang包下.java提供了5个基本Annotation的用法.使用Annotation时要在其前面加@符号,并把Annotation当成一个修饰符使用,用于修饰它支持的程序元素.
5个基本的Annotation有:@Override@Deprecated@Suppress Warnings@Safe Varargs@FunctionalInterface
1.@Override 限定重写父类的方法.####
这个东西在平常开发中非常常见.在这之前我只是知道这是个系统方法.@Override实际上是用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法.主要的作用是告诉编译器检查这个方法.保证父类包含一个被该方法重写的方法,否则就会编译出错.对于程序员来说,主要是帮助程序员避免一些低级错误.
看@Override的代码可以知道@Override被@Target(ElementType.METHOD)修饰,所以@Override只能用来修饰方法.注解信息被保存在源文件中.
2.@Deprecated 标示已过时.####
<pre>
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated{
}
</pre>
这个注解会在运行时有效,会引起编译器的警告.那如何抑制编译器的警告呢.
3.@SuppressWarnings 抑制编译器警告.####
这个东西大家也不陌生.@SuppressWarnings会一直作用于该程序元素的所有子元素.大家可能注意到每次使用这个注解,系统都会提示是放在方法上还是某个类上面.反正什么地方,@SuppressWarnings的作用范围就是什么.这里我注意到,当我在一个方法上添加@SuppressWarnings注解时,系统有如下提示
可以到看@SuppressWarnings里的值是一个数组类型的.所以说这里是可以放置多个值共同作用的.
在疯狂Java中我看到作者说@SuppressWarnings的值必须写成name=value格式(也就是注解值的标准格式),但是我在实际中见到的并不是,比如说写成@SuppressWarnings("deprecation")-->抑制过时警告. 就可以这么写
其实这里有一个是系统帮你做了.就是name省略了.name可以省略的前提是注解里只有一个变量.我们可以看下@SuppressWarnings的代码
<pre>
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,
ElementType.PARAMETER,ElementType.CONSTRUCTOR,
ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings{
/**
- The list of warnings a compiler should not issue.
*/
public String[] value();
}
</pre>
可以看到里面只有一个value,所以说其实直接写值就可以了.
4.@SafeVarargs Java7"堆污染"警告和@Functionallnterface Java8的函数式接口####
这两个知道有这东西就行了.这里普及一下函数式接口.Java8中规定:如果接口中只有一个抽象方法(可以包含多个默认方法或者多个static方法),该接口就是函数式接口.@Functionallnterface就是用来指定某个接口必须是函数式接口.函数式接口就是为Java8的Lambda准备的,Java8允许使用Lambda表达式创建
PS:以下主要知识均来自疯狂java,其实以上也差不多,哈哈.总结嘛.######
四.学以致用,实现自定义Annotation###
1.定义不带成员变量的Annotation####
定义Annotation需要使用到@interface关键字
定义一个简单的注解
<pre>
//定义一个简单的注解
public @interface test{
}
</pre>
这种注解是可以放在程序的任何地方,因为注解上没有任何元注解修饰.当然我们这么写这个注解一点用都没有@Override是因为系统已经处理过了.
2. 定义带成员变量的Annotation####
<pre>
public @interface MyTag{
//定义带两个成员变量的Annotation
//Annotation中的成员变量以方法的形式来定义
String name();
int age();
}
</pre>
一旦Annotation中定义了成员变量,使用的时候就必须为成员变量赋值.
同时可以指定Annotation的默认值
<pre>
public @interface MyTag{
//定义带两个成员变量的Annotation
//Annotation中的成员变量以方法的形式来定义
Stringname() default "haha";
int age() default 9;
}
</pre>
这样就可以在使用的时候不指定值,一旦指定了值,将会覆盖默认值.
五.提取Annotation信息###
使用Annotation修饰,这些Annotation不会自己生效,必须由开发者提供相应的工具来提取并处理Annotation信息.
跳过书里的一些说明直接给出方法.
AnnotatedElement接口提供的抽象方法(在该接口的实现类中重写了这些方法):
方法1.<A extends Annotation> getAnnotation(Class<A> annotationClass)######
<A extends Annotation>为泛型参数声明,表明A的类型只能是Annotation类型或者是Annotation的子类。
返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null
方法2. Annotation[] getAnnotations()######
返回此元素上存在的所有注解,包括没有显示定义在该元素上的注解(继承得到的)。(如果此元素没有注释,则返回长度为零的数组。)
方法3. <A extends Annotation> getDeclaredAnnotation(Class<A> annotationClass)######
这是Java8新增的方法,该方法返回直接修饰该程序元素、指定类型的注解(忽略继承的注解)。如果该类型的注解不存在,返回null.
方法4. Annotation[] getDeclaredAnnotations()######
返回直接存在于此元素上的所有注解,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)
方法5. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)######
判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。
方法6. <A extends Annotation>T[] getAnnotationsByTpye(Class<A> annotationClass)######
因为java8增加了重复注解功能,因此需要使用该方法获得修饰该程序元素、指定类型的多个注解。
方法7. <A extends Annotation>T[] getDeclaredAnnotationsByTpye(Class<A> annotationClass)######
因为java8增加了重复注解功能,因此需要使用该方法获得直接修饰该程序元素、指定类型的多个注解。
这些方法不要刻意记住,大概有个印象就可以了.
用一个例子看一下.和书上的例子差不多.######
<pre>
//注解 注意这里必须加上这个Retention属性,并且值必须是RUNTIME.只有这样才能通过反射拿到值
@Retention(RetentionPolicy.RUNTIME)
public @ interface test{
String name() default"111";
}
</pre>
<pre>
//tes类
public class tes{
@test
public void info(){}
}
</pre>
<pre>
//随便找一个Activity
@Override
protected void onResume(){
super.onResume();
try{
//这里一点要写完整的类,不然会报找不到类的异常
Annotation[] array=Class.forName("com.example.wyfsh.myapplication.tes").getMethod("info").getAnnotations();
for(Annotation annotation:array){
Log.i("=================>",annotation+"");
}
}catch(Exceptione){
e.printStackTrace();
}
</pre>
输出结果.@com.example.wyfsh.myapplication.test() 输出的结果为tes类下info方法上所有的注解.
如果需要获取某个注解里的元数据,则需要将注解类型强制转换成所需的注解类型.然后通过注解对象的抽象方法来访问这些数据.
比如上面的获取test注解里的name.就可以这么写
@Override
protected void onResume(){
super.onResume();
try{
Annotation[] array=Class.forName("com.example.wyfsh.myapplication.tes").getMethod("info").getAnnotations();
for(Annotation annotation:array){
if(annotation instanceof test){
Log.i("=================>",((test)annotation).name()+"");
}
}
}catch(Exceptione){
e.printStackTrace();
}
}
打印结果:111
最后关于使用实例.建议去结合buttutter knife去学习.本来还有很多概念要说,但是比较啰嗦.所以结合这个基础知识去看buttutter knife的实现原理更有效.