作者: 蓝冰海域 | 来源:发表于2017-02-06 17:41 被阅读132次

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

    相关文章

      网友评论

        本文标题:

        本文链接:https://www.haomeiwen.com/subject/pujuittx.html