注解这个概念,有些人可能会有些陌生。但是撸过代码的人应该都见过@Override,有用过ButterKnife,Glide,GreenDao等等这些框架,当不用再写那么多行的findViewById,不用再为图片三级缓存加载而挠头,不再为写一条复杂的sql语句而烦恼的时候,我们不觉得对这些框架竖起了大拇指,感觉他们就像神灵一般解救我们于水火,而不敢去了解和深入他们。但是,如果知道了他们的原理,你会发现,我们也可是是神灵的缔造者。
注解(Annotation),也叫元数据(即描述数据的数据),一种代码级别的说明。
它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
说了一大堆,其实,就是比注释还牛逼的注释。
我们知道,注释是不会干预程序的执行的,注解同样不能影响代码的实际逻辑,但却可以起到辅助性的作用。
反正,就是很牛逼的。
比如:生成文档,像@param,@Document;编译检查,像Override;跟踪代码依赖性,替代配置文件,像eventBus,ButterKnife等。总之就是牛逼,就够了。
注解起源于java1.5,起初作用基本类似是加强型的注释语言,为了编译检查,增强可阅读性,比如@Override: 检查是否正确重写;@Deprecated: 表示该函数已弃用,会划一条横线;@NonNull: 检查参数非空。空指针可以在编译时发现,而不必等到运行时抛出NullPointerException。
在java1.6 的时候,官方推出了一个插入式注解处理API(有兴趣可以看下java新特性有兴趣可以看下java新特性:https://blog.csdn.net/sysmedia/article/details/53608681)。指的注意的是,对注解的处理不仅运行时可以处理,还可以在编译期处理,通过注解处理器生成新的java代码并加入到编译中。
一句话: 我们能用java注解自动生成java文件了。 哎呦,这个屌!不错呦!
这里插播一句,著名的butterknife框架,在早期的时候,使用的应该是运行时注解,所以,在效率性能方面会有影响。但是后期,JakeWharton修改为编译期注解,这样对程序的运行就不存在性能问题了。(因为早期听别人说,butterknife会影响性能,所以一直没有用,常年来一直在不停的findViewById,在慢慢的了解了注解后,才知道,大神早已经把性能的问题解决了,我才放心大胆的用了)
元注解,其实就是修饰注解的注解。
看下@Override 的定义,咱们就知道了
package java.lang;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Override本身是一个注解,但是它的定义上还有 @Target 和 @Retention 在修饰它,那么 像这两个用来 专门修饰注解的注解 就叫做元注解。
目前比较常见的元注解有:
参数 | 作用周期 |
---|---|
RetentionPolicy.SOURCE | 只在源码中有效,编译时抛弃,如@Override |
RetentionPolicy.CLASS | 编译期注解 |
RetentionPolicy.RUNTIME | 运行期注解 |
编译期注解 在class文件中可用,能够存于编译之后的字节码之中(编译时注解能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。这些操作是通过注解处理器完成的。Java编译器集成了注解处理、通过在编译期间调用javac -processor命令可以调起注解处理器,它能够允许我们实现编译时注解的功能,从而提高函数库的性能。)该注解的注册信息会保留在.java源码里和.class文件里,在执行的时候,会被Java虚拟机丢弃,不会加载到虚拟机中。
运行期注解 VM在运行期也会保留注解,因此运行期注解可以通过反射获取注解的相关信息(运行时注解一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简答。)Java虚拟机在运行期也保留注解信息,可以通过反射机制读取注解的信息(.java源码,.class文件和执行的时候都有注解的信息)
@Target:注解对象的作用范围。
参数 | 作用范围 |
---|---|
ElementType.TYPE | 类、接口、枚举、注解类型 |
ElementType.FIELD | 类成员属性(构造方法、方法、成员变量) |
ElementType.METHOD | 方法 |
ElementType.PARAMETER | 参数 |
ElementType.CONSTRUCTOR | 构造参数 |
ElementType.LOCAL_VARIABLE | 局部变量 |
ElementType.ANNOTATION_TYPE | 注解 |
ElementType.PACKAGE | 包 |
ElementType.TYPE_PARAMETER | 类型参数 |
ElementType.TYPE_USE | 类型使用说明 |
@Inherited:标明所修饰的注解,在所作用的类上,是否可以被继承。
注解所作用的类,在继承时默认无法继承父类的注解。除非注解声明了 @Inherited。同时Inherited声明出来的注解,只对类有效,对方法/属性无效。
如其名,javadoc的工具文档化,一般不关心。
运行时注解 一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简单。并不是说为了性能运行期注解就不能用了,只能说不能滥用,要在性能方面给予考虑。目前主要的用到运行期注解的框架差不多都有缓存机制,只有在第一次使用时通过反射机制,当再次使用时直接从缓存中取出。
编译时注解 能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。通过注解处理器完成对注解的解析。Java编译器集成了注解处理器,允许实现编译期注解功能,调高函数库性能。
编译期: 是指把源码交给编译器编译成计算机可以执行的文件的过程.在Java中也就是把Java代码编成class文件的过程.编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误.优化代码神马的。
运行期:是把编译后的文件交给计算机执行.直到程序运行结束.所谓运行期就把在磁盘中的代码放到内存中执行起来。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MyTest {
String log();
}
注解的元素是以 接口方法 的形式体现的(但它真的是元素),方法声明不能包含任何参数或 throws,只能用public或默认(default)访问权修饰。
String log()
只表示MyTest这个注解有一个元素叫 log
方法的返回类型 必须是基本数据类型、String、Class、枚举、注解或这些类型的数组,方法可以有默认值(如果没有默认值,必须在属性中设置;Class 默认值不能为null) 如:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Hello {
String name() default "";
}
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface ClickInterval {
long value() default 1000;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyBindView {
int id() ;
}
调用时:
@MyBindView(id = R.id.tv_test)
public TextView tv_test;
如果参数有且只有一个,且参数名字是value,则,调用时候,可不写参数名称;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyBindView {
int value() ;
}
调用时:
@MyBindView(R.id.tv_test)
public TextView tv_test;
学习自定义运行时注解前,最好先了解下反射的相关api;
自定义注解 + 注解处理器(会用到java反射机制)
很简单!来做一个弱智版的butterknife吧
先搞个自定义注解:
既然是运行时注解,那 @Retention 就选择 RetentionPolicy.RUNTIME 了
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyBindView {
int value() ;
}
再怼个注解处理器:
public class MyButterKnife {
public static void bindView(Activity activity){
Class<? extends Activity> clazz = activity.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println("名称: " + field.getName());
if (field.isAnnotationPresent(MyBindView.class)) {
MyBindView annotation = field.getAnnotation(MyBindView.class);
if (annotation != null) {
try {
field.set(activity, activity.findViewById(annotation.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}else {
}
}
}
}
调用,测试下。完事!
public class MainActivity extends AppCompatActivity {
@MyBindView(R.id.tv_test)
public TextView tv_test;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyButterKnife.bindView(this);
Test();
}
public void Test() {
tv_test.setText("哈哈哈哈");
}
}
但是,如果你想要更高、更快、更强的话,你就需要学会自定义编译期注解了
毕竟这么简单的东西,拿出去装逼,有些丢人啊。
如果你想看更加有深度、有内涵、有料的东西,就看下一篇吧!
在此感谢以下文章的内容分享:
Android 自定义注解详解
Android 中优雅地使用注解
Android注解快速入门和实用解析