注解

2021-02-19  本文已影响0人  crossroads

前言

注解,提高开发效率,不一定提高程序运行效率。今天就学习下简化代码的注解把。
先看个示例,对照着学

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    int code() default -1;
    ThreadMode threadMode() default ThreadMode.CURRENT_THREAD;
}

一、注解的语法和定义形式

1.以@interface关键字定义

  1. 注解包含成员,成员以无参数的方法形式声明,其方法名和返回值定义了该成员的名字和类型。
  2. 成员赋值通过@Annotation(name=value)的形式
  3. 注解通过元注解@Retention、 @Target、@Inherited、@Documented来表明注解的生命周期、注解的修饰目标、直接可否被继承、注解被javadoc此类的工具文档化。
    上面的示例,可以看出:
  4. @Retention的值为RetentionPolicy.RUNTIME,@Target的值为ElementType.METHOD
  5. 成员名称为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. 找到类对应的所有属性或方法(看自定义的注解是定义方法上还是属性上)
  2. 找到添加了注解的属性或者方法, 做注解需要自定义的一些操作
    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。

后记

整理总结于网址

上一篇下一篇

猜你喜欢

热点阅读