文章目录
什么是注解(Annotation)注解有什么用注解怎么用注解关键字 @interface注解属性的数据类型 元注解@Retention@Target@Documented@Inherited@Repeatable Java内置的注解如何获取注解反射获取运行期注解结合反射与注解实现类似ButterKnife的功能反射处理注解的缺点 APT处理编译期注解什么是注解(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、@Repeatable5 种。
@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()还在@Apublic class Test {public Test() {}}//但是运行时却获取不到注解AAnnotation[] 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)@Inheritedpublic @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: Buttonoverride 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() {@Overridepublic void onClick(View v) {try {m.setAccessible(true);m.invoke(activity, view);} catch (Exception e) {e.printStackTrace();}}});}}}}}
反射处理注解的缺点
只能处理运行期注解;
效率不高,影响程序性能。
(通过反射注解方式绑定view,在view数量少的情况还好。但是在实际项目中一个Activity可能需要绑定十几二十个view,而且还可能有数十个这样的Activity。这样情况下使用反射是非常不明智的选择)
ButterKnife的核心不是用反射,而是用APT。
APT处理编译期注解
注解处理器(Annotation Processing Tool)简称APT。它是javac的一个工具。
作用:APT可以用来在编译时扫描和处理注解。
应用场景:在框架中广泛运用,比如:ButterKnife、EventBus、Dagger2、ARouter…
APT内容我单独整理了一个笔记
Java注解处理器学习记录(实现乞丐版ButterKnife)
如果觉得《Java注解学习记录(反射也能实现ButterKnife)》对你有帮助,请点赞、收藏,并留下你的观点哦!