文章目录
什么是注解(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();
}
}
});
}
}
}
}
}
反射处理注解的缺点
-
只能处理运行期注解;
-
效率不高,影响程序性能。
(通过反射注解方式绑定view,在view数量少的情况还好。但是在实际项目中一个Activity可能需要绑定十几二十个view,而且还可能有数十个这样的Activity。这样情况下使用反射是非常不明智的选择)
ButterKnife的核心不是用反射,而是用APT。
APT处理编译期注解
注解处理器(Annotation Processing Tool)简称APT。它是javac的一个工具。
作用:APT可以用来在编译时扫描和处理注解。
应用场景:在框架中广泛运用,比如:ButterKnife、EventBus、Dagger2、ARouter…
APT内容我单独整理了一个笔记
Java注解处理器学习记录(实现乞丐版ButterKnife)