上一篇讲解了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 | 表示注解类型能够被自动继承 |
@Repeatable | java1.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注解使用可以,参考这篇文章:
写得不错的~~~
注解的适用场景
说了这么多,那么注解的适用场景是什么了?用在哪里了?
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。现在我们可以知道了,注解是给编译器或者APT使用。
Effective Java参考
以下为该书第35条~第37条关于注解的总结,都是大佬们的经验~
注解优先大于命名模式。使用命名模式,文字拼写错误会导致失败,且没有任何提示,无法确保只适用于相应的元素上。
坚持使用Override注解,可以防止一大类的非法错误。
用
标记接口
定义类型,标记接口是没有方法声明的接口,而只是指明一个类实现了具有某种属性的接口。标记接口比标记注解的两点好处,1.标记接口定义的类型是由被标记类的实例实现,标记注解没有定义这样的类型。2.标记接口可以更加精确的被锁定,可以被扩展成它适用的接口。
参考
最后
注解的使用就介绍到此为止,下一篇将介绍编译时注解-注解处理器的使用。