Java注解学习记录(反射也能实现ButterKnife)

什么是注解(Annotation)

官方:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java1.5 开始添加到 Java 的。

通俗:注解就是给代码贴上标签。


注解有什么用

提供信息给编译器: 编译器可以利用注解来探测错误和警告信息

编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。

运行时的处理: 某些注解可以在程序运行的时候接受代码的提取


注解怎么用

要牢记,只要用到注解,必然有三点定义注解使用注解获取注解


注解关键字 @interface
//定义注解
@Retention(RetentionPolicy.RUNTIME)
public @interface A {
    String a();
}

//使用注解
@A(a = "test")
public class Test { }

//获取注解
Annotation[] annotation = Test.class.getAnnotations();
System.out.println(Arrays.toString(annotation));

注解属性的数据类型
  • 八种基本数据类型
  • String
  • 枚举
  • Class
  • 注解类型
  • 以上类型的一维数组

元注解

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。

元标签有 @Retention@Documented@Target@Inherited@Repeatable 5 种。


@Retention

Retention 意为保留期。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。

RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。

RetentionPolicy.RUNTIME 注解可以保留到运行时,所以在程序运行时可以获取到它们

//RetentionPolicy.SOURCE ==========================================================
@Retention(RetentionPolicy.SOURCE)
public @interface A { String s() default "我是默认值"; }

//Test.java
@A()
public class Test { }

//Test.class,@A()被磨掉了
public class Test {
    public Test() { }
}

//RetentionPolicy.CLASS ==========================================================
@Retention(RetentionPolicy.CLASS)
public @interface A { String s() default "我是默认值"; }

//Test.class,@A()还在
@A
public class Test {
    public Test() { }
}

//但是运行时却获取不到注解A
Annotation[] annotation = Test.class.getAnnotations();
System.out.println(Arrays.toString(annotation)); //输出 -> []


//RetentionPolicy.RUNTIME ==========================================================
@Retention(RetentionPolicy.RUNTIME)
public @interface A { String s() default "我是默认值"; }

//运行时获取到注解A了
Annotation[] annotation = Test.class.getAnnotations();
System.out.println(Arrays.toString(annotation)); //输出 -> [@com.llk.kt.annotation.A(s=我是默认值)]

@Target

Target 意为目标,@Target 指定了注解的使用的场景

public enum ElementType {
    /** 用于描述类、接口(包括注解类型) 或enum声明 */
    TYPE,
  
    /** 用于描述属性 */
    FIELD,
  
    /** 用于描述方法 */
    METHOD,
  
    /** 用于描述参数 */
    PARAMETER,
  
    /** 用于描述构造器 */
    CONSTRUCTOR,
  
    /** 用于描述局部变量 */
    LOCAL_VARIABLE,
  
    /** 用于描述注解类型 */
    ANNOTATION_TYPE,
  
    /** 用于描述包 */
    PACKAGE,
  
    /**
     * 用来标注类型参数
     * @since 1.8
     */
    TYPE_PARAMETER,
  
    /**
     * 能标注任何类型名称
     * @since 1.8
     */
    TYPE_USE
}

@Documented

这个元注解与文档有关,它的作用是能够将注解中的元素包含到 Javadoc 中去。(基本用不上)


@Inherited

Inherited 意为继承,如果父注解了@Inherited,那么它的子类没有被任何注解应用的话,那么这个子类就继承了父类的注解。

@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface A {
    String a() default "赛亚人";
}

@A(a = "超级赛亚人")
public class Test { }

public class Test2 extends Test{}

//输出 -> [@com.llk.kt.annotation.A(a=超级赛亚人)]
//如果没有加@Inherited 输出 -> []
Annotation[] annotation = Test2.class.getAnnotations();
System.out.println(Arrays.toString(annotation));

@Repeatable

Repeatable 意为可重复,@Repeatable 是 Java 1.8 才加进来的。

解决同一个注解不能重复在同一类/方法/属性上使用的问题

@Retention(RetentionPolicy.RUNTIME)
public @interface B {
    A[] value();
}

@Repeatable(B.class)
public @interface A {
    String s() default "请添加描述";
}

@A(s = "超级赛亚人")
@A(s = "奥特曼")
@A(s = "橡胶果实能力者")
public class Test { }

//输出 -> [@com.llk.kt.annotation.B(value=[@com.llk.kt.annotation.A(s=超级赛亚人), @com.llk.kt.annotation.A(s=奥特曼), @com.llk.kt.annotation.A(s=橡胶果实能力者)])]
Annotation[] annotation = Test.class.getAnnotations();
System.out.println(Arrays.toString(annotation));

Java内置的注解

  • @Override - 检查该方法是否是重写方法。如果发现其父类或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。

如何获取注解

反射获取运行期注解

AnnotatedElement接口是获取注解信息的接口

public interface AnnotatedElement {

//如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。
public default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { throw new RuntimeException("Stub!"); }

//获取当前类的指定注解(包括父类的)
public <T extends Annotation> T getAnnotation(Class<T> annotationClass);

//获取当前类的所有注解(包括父类的)  
public Annotation getAnnotations();

//获取当前类的所有指定注解(包括父类的)
public default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) { throw new RuntimeException("Stub!"); }

//获取当前类的指定注解
public default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) { throw new RuntimeException("Stub!"); }

//获取当前类的所有指定注解
//如果如果使用了元注解@Repeatable(Java1.8)的话,能实现同一个元素上有多个相同的注解
public default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) { throw new RuntimeException("Stub!"); }

//获取当前类的所有注解
public Annotation getDeclaredAnnotations();
}

Class、Constructor、Method、Field这些类都实现了AnnotatedElement接口。其中Class是直接实现的,其他都是继承了AccessibleObject类(它实现了AnnotatedElement接口)。

所以基本上反射常用的类都具备获取注解的能。


结合反射与注解实现类似ButterKnife的功能

先康康Activity中的实现,@BindLayout负责setContentLayout,@BindView负责findViewById,@BindClick负责setOnClickListener。BindUtils.bind(this)是触发解析注解的方法。

@BindLayout(id = R.layout.activity_main)
class MainActivity : AppCompatActivity() {

    @BindView(id = R.id.btn)
    lateinit var btn: Button

    @BindView(id = R.id.btn2)
    lateinit var btn2: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        BindUtils.bind(this)
        btn.text = "我是1"
        btn2.text = "我是2"
    }

    @BindClick(ids = [R.id.btn, R.id.btn2])
    fun click(v: View){
        when (v.id){
            R.id.btn -> Toast.makeText(this, "我按了1", Toast.LENGTH_SHORT).show()
            R.id.btn2 -> Toast.makeText(this, "我按了2", Toast.LENGTH_SHORT).show()
        }
    }
}

在康康注解的实现

@Retention(RetentionPolicy.RUNTIME) //保留期是运行时
@Target(value = ElementType.TYPE) //目标为类
public @interface BindLayout {
    int id() default -1;
}

@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD) //目标为属性
public @interface BindView {
    int id() default -1;
}

@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD) //目标为方法
public @interface BindClick {
    int[] ids(); //接受id数组
}

最后康康解析注解的BindUtils,都是写反射的基操。

public class BindUtils {
    public static void bind(Activity activity){
        if (bindLayout(activity)){
            bindView(activity);
            bindOnClick(activity);
        }
    }

    private static boolean bindLayout(Activity activity){
        Class clazz = activity.getClass();
        if (clazz.isAnnotationPresent(BindLayout.class)){
            BindLayout bindLayout = (BindLayout) clazz.getAnnotation(BindLayout.class);
            if (bindLayout.id() == -1) return false;
            activity.setContentView(bindLayout.id());
        }
        return true;
    }

    private static void bindView(Activity activity){
        Class clazz = activity.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field f : fields){
            if (f.isAnnotationPresent(BindView.class)){
                try {
                    BindView bindView = f.getAnnotation(BindView.class);
                    if (bindView.id() == -1) continue;
                    View view = activity.findViewById(bindView.id());
                    if (view == null) continue;
                    f.setAccessible(true);
                    f.set(activity, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void bindOnClick(Activity activity){
        Class clazz = activity.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method m : methods){
            if (m.isAnnotationPresent(BindClick.class)){
                BindClick mOnclick = m.getAnnotation(BindClick.class);
                int[] ids = mOnclick.ids();
                for (int i = 0; i < ids.length; i++){
                    final View view = activity.findViewById(ids[i]);
                    if(view == null) continue;
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            try {
                                m.setAccessible(true);
                                m.invoke(activity, view);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }
    }
}

反射处理注解的缺点
  1. 只能处理运行期注解;

  2. 效率不高,影响程序性能。

    (通过反射注解方式绑定view,在view数量少的情况还好。但是在实际项目中一个Activity可能需要绑定十几二十个view,而且还可能有数十个这样的Activity。这样情况下使用反射是非常不明智的选择)

ButterKnife的核心不是用反射,而是用APT。


APT处理编译期注解

注解处理器(Annotation Processing Tool)简称APT。它是javac的一个工具。
作用:APT可以用来在编译时扫描和处理注解。
应用场景:在框架中广泛运用,比如:ButterKnife、EventBus、Dagger2、ARouter…

APT内容我单独整理了一个笔记
Java注解处理器学习记录(实现乞丐版ButterKnife)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值