初步了解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{

}

可能有的人就问了,你这都是啥啥啥.这东西可是非常的常见.再看

代码


@Override

protected voidonStart(){

super.onStart();

}

发现没有.这个点进去就是. @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: 用于描述构造器

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

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

4 . METHOD:用于描述方法

5.PACKAGE:用于描述包

6 . PARAMETER:用于描述参数

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

8 . 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 标示已过时.


@Documented

@Retention(RetentionPolicy.RUNTIME)

public @interface Deprecated{

}

这个注解会在运行时有效,会引起编译器的警告.那如何抑制编译器的警告呢.

3.@SuppressWarnings 抑制编译器警告.

这个东西大家也不陌生.@SuppressWarnings会一直作用于该程序元素的所有子元素.大家可能注意到每次使用这个注解,系统都会提示是放在方法上还是某个类上面.反正什么地方,@SuppressWarnings的作用范围就是什么.这里我注意到,当我在一个方法上添加@SuppressWarnings注解时,系统有如下提示

可以到看@SuppressWarnings里的值是一个数组类型的.所以说这里是可以放置多个值共同作用的.

在疯狂Java中我看到作者说@SuppressWarnings的值必须写成name=value格式(也就是注解值的标准格式),但是我在实际中见到的并不是,比如说写成@SuppressWarnings(“deprecation”)–>抑制过时警告. 就可以这么写

其实这里有一个是系统帮你做了.就是name省略了.name可以省略的前提是注解里只有一个变量.我们可以看下@SuppressWarnings的代码

@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();
}

可以看到里面只有一个value,所以说其实直接写值就可以了.

4.@SafeVarargs Java7”堆污染”警告和@Functionallnterface Java8的函数式接口

这两个知道有这东西就行了.这里普及一下函数式接口.Java8中规定:如果接口中只有一个抽象方法(可以包含多个默认方法或者多个static方法),该接口就是函数式接口.@Functionallnterface就是用来指定某个接口必须是函数式接口.函数式接口就是为Java8的Lambda准备的,Java8允许使用Lambda表达式创建

PS:以下主要知识均来自疯狂java,其实以上也差不多,哈哈.总结嘛.

四.学以致用,实现自定义Annotation

1.定义不带成员变量的Annotation

定义Annotation需要使用到@interface关键字

定义一个简单的注解


//定义一个简单的注解

public @interface test{

}

这种注解是可以放在程序的任何地方,因为注解上没有任何元注解修饰.当然我们这么写这个注解一点用都没有@Override是因为系统已经处理过了.

2. 定义带成员变量的Annotation


public @interface MyTag{

//定义带两个成员变量的Annotation

//Annotation中的成员变量以方法的形式来定义

String name();

int age();

}

一旦Annotation中定义了成员变量,使用的时候就必须为成员变量赋值.

同时可以指定Annotation的默认值


public @interface MyTag{

//定义带两个成员变量的Annotation

//Annotation中的成员变量以方法的形式来定义

Stringname() default "haha";

int age() default 9;

}

这样就可以在使用的时候不指定值,一旦指定了值,将会覆盖默认值.

五.提取Annotation信息

使用Annotation修饰,这些Annotation不会自己生效,必须由开发者提供相应的工具来提取并处理Annotation信息.

跳过书里的一些说明直接给出方法.

AnnotatedElement接口提供的抽象方法(在该接口的实现类中重写了这些方法):

方法1.<T 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增加了重复注解功能,因此需要使用该方法获得直接修饰该程序元素、指定类型的多个注解。

这些方法不要刻意记住,大概有个印象就可以了.

用一个例子看一下.和书上的例子差不多


//注解 注意这里必须加上这个Retention属性,并且值必须是RUNTIME.只有这样才能通过反射拿到值

@Retention(RetentionPolicy.RUNTIME)

public @ interface test{

String name() default"111";

}
//tes类
public class tes{
@test
public void info(){}
}

//随便找一个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();
}

输出结果.@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的实现原理更有效.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值