注解的使用(二)

上一篇讲解了javapoet的使用,接下来本篇博文将详细记录下注解以及自定义注解的使用。

在Android开发中,注解的有效使用,可以提高程序的开发效率,现在比较流行的框架:
butterknife免去我们view控件的定义和初始化。
EventBus3方便我们实现组件通信
dagger模块解耦,依赖注入
基本都使用到注解,来提高程序编码效率

百度百科定义

注解(Annotation)
也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用:
①编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
②代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】

说在前面的感受

我在学编程到现在基本只是用到了java和android内置提供的注解,没怎么用自定义注解,不过当我写代码到一定程度时,经常会使用那些第三方库,注解经常见到,越来越体会到注解的强大之处,慢慢想了解下内部的使用原理,因为在平时开发中,对于一个初级的开发工程师,代码的重复度都是比较高的。最近在写一个项目的网络访问模块时,上一个程序员用的是mvp设计架构,感觉简单是简单了,但是每一次网络访问都会重写好多Model代码,重复度太高了。每次使用基本都是复制大半,然后改下URL地址,和基本的方法参数,就完成了。这样虽然能完成任务,但是最近想封装下,重构下代码,于是这些时一直在看注解的使用,希望能从中找到解决的思路。

这里写图片描述


语法

1).定义一个注解

注解通过@interface标识符来定义

public @interface TestAnnotation {
}

注解和接口类似,只不过interface前面多了一个@,注解的名称为TestAnnotation

2).给注解添加属性
我们定义了一个注解之后,就需要给注解添加属性,当然我们可以不用给注解添加属性,没有属性的注解称为标识注解。注解没有方法,只有属性,也被称为成员变量。

public @interface TestAnnotation {
    String value();
    String name();
}

注解属性以“无形参的方法”形式来定义,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

3).使用
只需要在相应的对象,属性或方法之上添加 @+注解名即可

@TestAnnotation(name = "chen", value = "annotation")
public class Test {
}

注解只有一个属性时,必须为value属性

@TestAnnotation(value = "annotation")
public class Test {
}

只有一个参数时,value也可以省去

@TestAnnotation("annotation")
public class Test {
}

4).默认值
注解可以有默认值,我们在使用时可以不用给参数赋值

...
String value() default "annotation";
String name() default "chen";

//使用时可以不用传值,当然也可以传值覆盖默认值
@TestAnnotation()
public class Test {
}

注意:
1).没有属性时,可以直接省略括号

@TestAnnotation
public class Test {
}

2).另外,注解的属性类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组


元注解

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。在java中元注解有5种,@Retention、@Documented、@Target、@Inherited、@Repeatable。

注解类型含义
@Retention注解的保留时长
@Documented能够将注解的元素包含到javadoc中去
@Target注解运用的地方
@Inherited表示注解类型能够被自动继承
@Repeatablejava1.8加进来的,新特性,表示注解的值可以同时取多个

1).@Retention

注解的保留时长,取决于RetentionPolicy,有三种取值方式,分别代表三种注解保留时长方式。

保留时长含义
SOURCE只在源码阶段存在,编译器编译时将会丢失掉。
CLASS在编译阶段会保留,但是在运行时不会加载到JVM虚拟机中。我们没有指定保留方式时,这是默认的保留方式。
RUNTIME注解在编译和运行时,会被保留,这是生命周期非常长的一种保留方式,我们可以通过反射读取注解的值。

eg:

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation 

很轻松的可以知道,我们将注解的保留时长为运行时,此时我们是可以通过反射获取到的,但是一般不建议,因为运行时注解,通过反射获取会造成一定运行效率的损耗,后面会单独写一篇基于编译时注解的文章。不过后面还是会介绍下利用反射提取注解里面的元素和值。

2). @Target

表示该注解类型支持的所使用的程序元素类型。当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。关于程序元素(ElementType)是枚举类型,共定义10种程序元素

元素类型含义
TYPE类,接口(包括注解),枚举的声明
FIELD属性字段(包括枚举常量) 声明
METHOD方法声明
PARAMETER参数申明
CONSTRUCTOR构造方法
LOCAL_VARIABLE局部变量声明
ANNOTATION_TYPE注解声明
PACKAGE包声明
TYPE_PARAMETER类型参数,1.8之后
TYPE_USE使用类型,1.8之后

eg:

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation

注意:没有指定时默认支持全部

3). @Inherited

表示注解类型能够被自动继承,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

eg:

@TestAnnotation
public class Test {

    class Test2 extends Test{

    }
}

@TestAnnotation注解Test类,那么他的子类Test2也被注解了。

4).@Repeatable

表示注解的值可以同时取多个,是java1.8新加进来的

eg:

//
@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {
    TestAnnotation[] value();
}

//
@Repeatable(Persons.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String value() default "annotation";
    String name() default "chen";
}

//
@TestAnnotation(value = "c", name = "n")
@TestAnnotation
public class Test

`@Repeatable注解了TestAnnotation 注解,而@Repeatable后面括号里面的类相当于容器注解,也就是用来存放其它注解的地方,它本身也是一个注解,而这个容器注解里面是数组。容器里面的是一个被 @Repeatable 注解过的注解数组,注意它是数组。

~~~楼主感觉这里有点绕,得多理解。


注解提取

前面只是介绍了注解的基本使用和语法,接下来就来介绍下如何提取注解里面的值。注意,因为类,接口,方法,字段上所用到的注解提取方法是一样的,所以这里主要介绍下类上注解的提取。

判断一个类上是否应用了注解可以使用class的isAnnotationPresent

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

获取注解对象可以使用getAnnotation

public <A extends Annotation> A getAnnotation(Class<A> annotationClass)

获取一个元素上所有的注解 getAnnotations

public Annotation[] getAnnotations()

只要获取到的注解对象不为空,就可以调用里面的属性了。

eg:

@TestAnnotation(value = "这是注解值", name = "这是注解名")
public class Test {

    public static void main(String[] args){
        try {
            Class<?> clazz = Class.forName("com.narkang.annotation.Test");
            boolean isAnn = clazz.isAnnotationPresent(TestAnnotation.class);
            if(isAnn){
                TestAnnotation testAnnotation = clazz.getAnnotation(TestAnnotation.class);
                System.out.println("value="+testAnnotation.value()+" name="+testAnnotation.name());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

result:
这里写图片描述

对于方法,字段等其他就不赘述了,基本都一样~~~


java内置注解

  • @Deprecated

它是一个标记注解,在源程序加入这个注解之后,并不影响程序的编译;@Deprecated,如果某个类或成员出现这个词,表示编译器不建议使用这个注解,这个API可能在未来的JDK版本中删除。现在还保留的,是为了给已经使用这些API程序一个缓冲期,如果现在就删除了,这些程序就可能无法在新的编译器中编译运行了。

这是它的结构:
这里写图片描述

我们可以知道它的适用范围

eg:
这里写图片描述

这里写图片描述

还未编译时,这个只是提示用户该API已经被弃用了,并不会影响编译,将来的某个时刻,可能会被弃用,最好不要再使用,对于将来会被弃用的API,一般会提供新的解决方案。

  • @Override

这个注解我们基本天天打交道,就是要覆写父类的方法,不要求强制加,但是最好建议加上此注解。

eg:

public class TestOverride {

    public void testSout(){
        System.out.println("父类");
    }

    static class Child extends TestOverride{
        public void testSout() {
            System.out.println("子类");
        }
    }

    public static void main(String[] args){
        TestOverride testOverride = new Child();
        testOverride.testSout();
    }
}

此时程序的输出:
这里写图片描述

而如果程序子类方法改了,比如这样:

public void testSout2() {
    System.out.println("子类");
}

此时程序的输出:
这里写图片描述

这显然不是我们需要的,一般如果我们没有添加此注解,那么我们在编写代码时,可能会不知觉的改了重写的代码,此时编译器并不会报错,而是当一个新的方法处理,所以一般建议加上此注解。

  • @ SuppressWarnings

当我们的一个方法调用了弃用的方法或者进行不安全的类型转换,编译器会生成警告。我们可以为这个方法增加@SuppressWarnings注解,来抑制编译器生成警告。

该注解接收一个value数组,数组内容为我们要抑制的警告类型。


android内置注解

关于Android注解使用可以,参考这篇文章:

Android-注解详解

写得不错的~~~

这里写图片描述


注解的适用场景

说了这么多,那么注解的适用场景是什么了?用在哪里了?

注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。

注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取

当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。现在我们可以知道了,注解是给编译器或者APT使用。


Effective Java参考

以下为该书第35条~第37条关于注解的总结,都是大佬们的经验~

  1. 注解优先大于命名模式。使用命名模式,文字拼写错误会导致失败,且没有任何提示,无法确保只适用于相应的元素上。

  2. 坚持使用Override注解,可以防止一大类的非法错误。

  3. 标记接口定义类型,标记接口是没有方法声明的接口,而只是指明一个类实现了具有某种属性的接口。标记接口比标记注解的两点好处,1.标记接口定义的类型是由被标记类的实例实现,标记注解没有定义这样的类型。2.标记接口可以更加精确的被锁定,可以被扩展成它适用的接口。


参考

  1. http://blog.csdn.net/briblue/article/details/73824058

  2. http://blog.csdn.net/column/details/13413.html


最后

注解的使用就介绍到此为止,下一篇将介绍编译时注解-注解处理器的使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值