初步了解Annotation(注解)

看了郭神公众号推荐的注解知识,想到自己这方面有欠缺,大家一起来学习.本文主要是对自己学到的知识进行总结,和大家分享自己学到的东西.这里给出我看的文章地址一小时搞明白自定义注解.我读完了脑袋还是蒙的,不是作者写的不好,而是读完了脑子蒙蒙的没有啥印象..所以我百度看了一下相关知识,看了疯狂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: 用于描述构造器

  1. FIELD:用于描述域.这里比较模糊.查了书上的说明是指定Annotation只能修饰成员变量.

  2. LOCAL_VARIABLE:用于描述局部变量

  3. METHOD:用于描述方法

  4. PACKAGE:用于描述包

  5. PARAMETER:用于描述参数

  6. TYPE:用于描述类、接口(包括注解类型) 或enum(枚举)声明

  7. 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的实现原理更有效.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容