语法
自定义注解类似创建一个接口,但它是用关键字@interface来声明的,如:
public @interface MyAnnotation {
}
给自定义注解添加变量,可以这样:
public @interface MyAnnotation {
String value();
}
如果注解只有一个成员,并且把成员取名为value(),则在使用时可以不用指定成员名像@SuppviseWarnings注解一样使用;如果成员名不为value,则使用时需指明成员名,所以刚才的注解我们要调用如下:
@MyAnnotation("hello")
private String str;
// 或者
@MyAnnotation(value = "hello")
private String str2;
如果注解中的一个成员想给它设置成一个默认值,可以这样:
public @interface MyAnnotation {
Stringvalue();
enum Color{BLUE, RED, GREEN}
Colorcolor() default Color.RED;
}
这时的调用应该如下:
@MyAnnotation("hello")
private String str;
// 或者
@MyAnnotation(value = "hello")
private String str2;
// 或者
@MyAnnotation(value = "hello", color =MyAnnotation.Color.BLUE)
private String str3;
注意,下面的调用是错误的:
@MyAnnotation("hello", color =MyAnnotation.Color.BLUE)
private String str4;
元注解
Java5.0定义了4个标准的元注解,它们是用来专门定义其它注解的注解,这4个元注解分别是:@Target、@Retention、@Inherited和@Documented。
@Target
用于描述注解的使用范围,即被描述的注解可以用在什么地方,取值有:
ElemenetType.CONSTRUCTOR----------------------------描述构造器
ElemenetType.FIELD -----------------------------------------描述域、字段(包括enum 实例)
ElemenetType.LOCAL_VARIABLE-------------------------描述局部变量
ElemenetType.METHOD ------------------------------------描述方法
ElemenetType.PACKAGE ------------------------------------描述包
ElemenetType.PARAMETER -------------------------------描述参数
ElemenetType.TYPE-------------------------------------------描述类、接口(包括注解类型)或enum声明
示例:
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface MyAnnotation {
Stringvalue();
}
注意,因为上面定义注解时候,@Target只包含了ElemenetType.TYPE和ElemenetType.FIELD,所以在类和声明中注解是可以的,而在方法上注解会报错。
@Retention
用于描述注解的生命周期,即:被描述的注解在什么范围内有效,取值有:
RetentionPolicy.SOURCE---------------------------在源文件中有效,编译时会丢失
RetentionPolicy.CLASS-------------------------------在class文件中有效,运行时忽略
RetentionPolicy.RUNTIME-------------------------在运行时有效,可以通过反射读取
示例:
//@Retention(RetentionPolicy.SOURCE) // 在源文件中有效,编译时会丢失
//@Retention(RetentionPolicy.CLASS) // 在class文件中有效,运行时忽略
@Retention(RetentionPolicy.RUNTIME) // 运行时有效,可以通过反射读取
public @interface MyAnnotation {
Stringvalue();
}
@Inherited
是一个标记注解,它是没用成员的。用于表示一个类使用了该注解,那么这个注解将被使用的类的子类继承拥有
示例:
@Inherited
public @interface MyAnnotation {
Stringvalue();
}
@Documented
是一个标记注解,它是没用成员的。用于描述其它类型的注解应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。简单地说,该注解只用于在导出javadoc时,生成一个说明。
示例:
@Documented
public @interface MyAnnotation {
String value();
}
创建自己的注解
如果我们声明了注解,但没有对它进行它本来的意义的实现,那么注解也只是仅仅是作为一种意义性的存在。注解的功能实现是通过反射来完成的,那么接下来我们就来通过示例来进行一个自定义注解的声明和实现工作。
View注入注解示例
一般情况下,我们会在onCreate中来给我们页面中的View来绑定对象,如:
public class MainActivity extends Activity {
private Button mBtn1;
private Button mBtn2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn1 = (Button)findViewById(R.id.btn1);
mBtn2 = (Button)findViewById(R.id.btn2);
}
}
如果页面中存在非常多的View,那么我们的代码中就会存在大量的findViewById(R.id.xxx);,所以我们最终使用注解的结果是这样:
public class MainActivity extends Activity {
@ViewInject(R.id.btn1)
private Button mBtn1;
@ViewInject(R.id.btn2)
private Button mBtn2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.injectViews(this);
}
}
通过在onCreate中使用了一行ViewUtils.injectViews(this);的代码,然后我们所有的View在声明中就可能通过@ViewInject注解来完成。
我们来看看@ViewInject注解的实现:
首先新建ViewInject文件:
@Target(ElementType.FIELD) // 描述字段
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
public @interface ViewInject {
int value(); // 只有一个value成员
}
接着新建一个ViewUtils类,然后实现injectViews方法:
public class ViewUtils {
public static void injectViews(Activity activity) {
Class activityClass = activity.getClass();
// 得到activity所有字段
Field[] fields = activityClass.getDeclaredFields();
for (Field field : fields) {
// 包括注解ViewInject的字段
if (field.isAnnotationPresent(ViewInject.class)) {
// 得到字段的ViewInject注解对象
ViewInject viewInject = field.getAnnotation(ViewInject.class);
// 得到注解的值(资源View的id)
int viewId = viewInject.value();
try {
// 反射Activity的findViewById方法
Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
findViewByIdMethod.setAccessible(true);
// 调用findViewById方法,并传入注解的值(资源View的id)
Object objectView = findViewByIdMethod.invoke(activity, viewId);
field.setAccessible(true);
// 给字段(View对象)赋值
field.set(activity, objectView);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
方法的逻辑可以通过上面的注释来了解,这里就不再说了。
总结
从示例中可以看到,自定义注解在实现逻辑上其实是使用了反射。我们知道,反射是需求代价的,如果大量使用反射会拖慢代码的效率,但使用注解的好处、代码整洁我也们也看到的。所以注解的使用没有好不好,只有合适不合适。