在Java1.5以后,引入了注解,也称作元数据。作为新的特性,同时也是基础知识之一,我们应该学会使用这种用法,虽然反射会带来代码效率问题,但相比于它的优点,这种损失我们还是可以承受的。
元数据被定义为:描述数据的数据,对数据及信息资源的描述性信息。
我们可以认为注解的目的就是对数据添加的附加信息。在java源代码中添加注解,有助于减轻编写“样板”代码的负担(findViewById),更加干净易读的代码以及编译器类型检查。
注解的语法比较简单,使用一个@符号修饰代表的就是一个注解。Java 5内置的几种注解有
//当前的方法定义将覆盖超类中的方法
@Override
//代表被这个元数据修饰的元素已被废弃,使用已废弃的方法或对象编译器会发出警告
@Deprecated
//关闭不当的编译器警告信息,比如unchecked,未检查的类型等
@SuppressWarnings
使用@interface来定义注解类型
public @interface Test {
}
使用注解类型
//一般写法,比较优美
@Test
void test(){
}
//注解可以看做是一种修饰符,它的使用和修饰符几乎一模一样
// 不太好看,不建议
public static synchronized @Test void test(){
}
上面简单定义了一个注解,但是一般我们定义的注解,还会定义一些注解的类型,Annotation有四种元注解类型,元注解专职负责注解其他的注解,详情可以看Java API里面的Annotation
# Retention
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
# Target
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
# Documented
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
# Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
Rentention
Rentention定义该注解在哪一个级别保留该注解信息,可选的RetentionType参数包含
// 存在于Java源文件,注解被编译器丢弃
SOURCE
// 存在于Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注解
CLASS
// 存在于源文件、编译生成的Class字节码文件,以及保留在运行时VM中,因此可通过反射读取注解
RUNTIME
Target
Target表示该注解可以用于什么地方,可能使用的参数ElementType包括
// 注解类型,表示这个注解只能用于注解类型
// 比如Target,Rentention,Inherited,Documented这些元注解都是用于注解类型的
ANNOTATION_TYPE
// 构造器声明
CONSTRUCTOR
// 字段声明(包括enum实例)
FIELD
// 局部变量声明
LOCAL_VARIABLE
// 方法声明
METHOD
// 包声明
PACKAGE
// 参数声明
PARAMETER
// 类,接口(包括注解类型)或者enum声明
TYPE
Documented
当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看该注解元素。
Inherited
允许子类继承父类中的注解
注解元素
在注解中还会包含一些元素表示值,当使用Class里面的方法分析处理注解的时候,程序就可以访问这些值。在注解中定义元素就像在普通接口中定义方法,但是注解可以使用default定义元素的默认值。对于没有元素的注解,我们可以把它作为标记来使用。比如被@Test标记的方法为测试方法
注解元素可用的类型包括如下几个
基本数据类型
String
Class
enum
Annotation
以上类型的数组
对于注解里面的元素,必须有一个确定的值,不能够使用null这种未定义的值作为默认值,所以我们可以使用空对象这样的概念来解决这个问题,比如定义空字符串作为字符串为null的情况。
解析注解
在很多ORM数据库框架中都使用了注解来定义Bean类,直接使用Bean类生成数据库表。比如ORMLite。
如果不对注解进行解析的话,其实注解就没什么意义了,可以通过Annotation中提供的API来访问注解。那么先来看一下Class提供给我们的用于解析注解的结构方法
// 如果当前元素包含指定的注解类型,则返回该注解对象,如果不存在则返回null
<A extends Annotation> getAnnotation(Class annotationClass)
// 返回这个元素上的所有注解
Annotation[] getAnnotations()
// 返回直接定义在这个元素上的注解
Annotation[] getDeclaredAnnotations()
// 如果当前这个元素包含指定的注解类型则返回true
boolean isAnnotationPresent(Class extends Annotation> annotationClass)
在Android中也有通过注解实现的IOC,我们在编写程序的时候,如果xml里面有很多很多的控件,这样就需要写很多遍findViewById,不但写起来很累,而且很占空间,于是人们就想到了通过注解来减轻这样的编写“样板”代码的负担。
ViewInject注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
int id() default -1;
}
BaseActivity基类,使用模板方法
public abstract class BaseActivity extends Activity {
private Context mContext;
private void inject() {
Class activity = getClass();
//获取Activity内所有的字段
Field[] fields = activity.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// 获取指定的注解类型,如果返回null则跳过
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
// 获取指定的属性
int value = viewInject.id();
try {
// 反射获取findViewById方法
Method method = activity.getMethod("findViewById", int.class);
method.setAccessible(true);
// 调用该方法,因为findViewById要求是在Activity对象上的方法
Object object = method.invoke(mContext, value);
field.set(mContext, object);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
protected abstract int requestLayout();
protected abstract void bindView();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(requestLayout());
mContext = this;
inject();
bindView();
}
}
MainActivity实现类
public class MainActivity extends BaseActivity {
@ViewInject(id=R.id.text)
private TextView textView;
@Override
protected int requestLayout() {
return R.layout.layout_annotation;
}
@Override
protected void bindView() {
textView.setText("1131");
}
}
现在大致上那些retrofit,dagger,butterknife使用的注解也是基于这个原理的罢,有空去好好研究一下这几个开源框架的源码,使用注解,我们应该还需要有类加载,泛型,反射等基础知识,才能够把注解玩的飞起。