前言
注解,提高开发效率,不一定提高程序运行效率。今天就学习下简化代码的注解把。
先看个示例,对照着学
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
int code() default -1;
ThreadMode threadMode() default ThreadMode.CURRENT_THREAD;
}
一、注解的语法和定义形式
1.以@interface关键字定义
- 注解包含成员,成员以无参数的方法形式声明,其方法名和返回值定义了该成员的名字和类型。
- 成员赋值通过@Annotation(name=value)的形式
- 注解通过元注解@Retention、 @Target、@Inherited、@Documented来表明注解的生命周期、注解的修饰目标、直接可否被继承、注解被javadoc此类的工具文档化。
上面的示例,可以看出: - @Retention的值为RetentionPolicy.RUNTIME,@Target的值为ElementType.METHOD
- 成员名称为code,类型为int,默认值-1
注解元素的默认值
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,表示某个元素不存在,在定义注解时,这已经成为一个习惯用法
二、元注解@Rententation @Target详解
1. @Retentation的值是enum类型,有三个可取值
public enum RetentionPolicy {
SOURCE,//注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
CLASS,//注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
RUNTIME;//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
private RetentionPolicy() {
}
}
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解
2. @Target的值也是enum类型,有八个可选值
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
public enum ElementType {
TYPE,//类、接口(包括注解类型)或枚举
FIELD,// 适用field属性,也包括enum常量
METHOD,//适用方法
PARAMETER,//方法参数
CONSTRUCTOR,//构造函数
LOCAL_VARIABLE,//局部变量
ANNOTATION_TYPE,//注解
PACKAGE,//包
TYPE_PARAMETER,//类型参数声明
TYPE_USE;//使用类型
private ElementType() {
}
}
三、注解解析
- 找到类对应的所有属性或方法(看自定义的注解是定义方法上还是属性上)
- 找到添加了注解的属性或者方法, 做注解需要自定义的一些操作
1. 如何找到类对应的属性和方法?
通过Class对象,例如
Class<?> subClass = obj.getClass();
Method[] methods = subClass.getDeclaredMethods();
这样就可以获取当前类所有的方法,即包括public、private和protected,注意不包括父类。
接下来,看看我们通过class对象都能获得什么?
/**
* 包名加类名
*/
public String getName();
/**
* 类名
*/
public String getSimpleName();
/**
* 返回当前类和父类层次的public构造方法
*/
public Constructor<?>[] getConstructors();
/**
* 返回当前类所有的构造方法(public、private和protected)
* 不包括父类
*/
public Constructor<?>[] getDeclaredConstructors();
/**
* 返回当前类所有public的字段,包括父类
*/
public Field[] getFields();
/**
* 返回当前类所有申明的字段,即包括public、private和protected,
* 不包括父类
*/
public native Field[] getDeclaredFields();
/**
* 返回当前类所有public的方法,包括父类
*/
public Method[] getMethods();
/**
* 返回当前类所有的方法,即包括public、private和protected,
* 不包括父类
*/
public Method[] getDeclaredMethods();
/**
* 获取局部或匿名内部类在定义时所在的方法
*/
public Method getEnclosingMethod();
/**
* 获取当前类的包
*/
public Package getPackage();
/**
* 获取当前类的直接超类的 Type
*/
public Type getGenericSuperclass();
/**
* 返回当前类直接实现的接口.不包含泛型参数信息
*/
public Class<?>[] getInterfaces();
/**
* 返回当前类的修饰符,public,private,protected
*/
public int getModifiers();
2. 如何找到添加了注解的属性或方法
Field和Method都实现了AnnotatedElement接口,通过这个接口就可以,例如
for (Method method : methods) {
// 指定类型Subscribe的注释是否存在于此元素上
if (method.isAnnotationPresent(Subscribe.class)) {
// 返回该元素上存在的指定类型的注解
Subscribe sub = method.getAnnotation(Subscribe.class);
int code = sub.code();//获得code值
//获得方法的参数类型
Class[] parameterType = method.getParameterTypes();
}
}
四、使用注解
@Subscribe(threadMode = ThreadMode.MAIN)
public void eventBus(EventEntity obj) {
}
五、示例
findviewById的注解
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CBindView {
@IdRes int value();
}
public class CButterKnife {
private CButterKnife() {
}
public static void bind(@NonNull Activity source) {
View sourceView = source.getWindow().getDecorView();
Field[] declaredFields = source.getClass().getDeclaredFields();//不包含父类的成员变量
for (Field field : declaredFields) {
if (field.isAnnotationPresent(CBindView.class)) {//注释是否存在于此元素上
CBindView bindView = field.getAnnotation(CBindView.class);
if (bindView != null) {
field.setAccessible(true);//允许修改反射属性
try {
//将source对象的这个field属性设置为新值sourceView.findViewById(bindView.value())
field.set(source, sourceView.findViewById(bindView.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
public class MainActivity2 extends AppCompatActivity {
@CBindView(R.id.title)
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
CButterKnife.bind(this);
textView.setText("测试");
}
}
这里顺便加个知识点吧,反射经常用到的除了field.set还有一个method.invoke,这个方法如method.invoke(action,args)调用action对象的myMethod方法,传参为args。
网友评论