一、注解的概念
注解(Annotation),也叫元数据(Metadata)。注解与类、接口、枚举在同一个层次,并可以应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中,用来对这些元素进行说明注释。
注解分类
基本内置注解,是指Java自带的几个Annotation,如@Override、Deprecated、@SuppressWarnings等;
元注解(meta-annotation),是指负责注解其他注解的注解,包含如下四个:
@Target
@Retention
@Documented
@Inherited
- 自定义注解,需要用到上面的元注解
二、注解的语法与定义形式
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.ANNOTATION_TYPE } )
public @interface Target {
ElementType[] value();
}
- 元注解@Retention,标明生命周期。
- 元注解@Target,标明修饰目标
- 成员名称为value,类型为ElementType[]
- 如果成员名称是value,在赋值过程中可以简写。如果成员类型为数组,但是只赋值一个元素,则也可以简写。
eg:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface RouterMap {
String value();
String[] registry() default {};
}
// 使用
@RouterMap(value = "HHH://XXX", registry = {"110"})
public class DownloadActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);
}
}
元注解说明
1、@Retention : 注解的生命周期
注解的值是 enum 类型的 RetentionPolicy,包括以下几种情况:
public enum RetentionPolicy {
/**
* 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃.
* 这意味着:Annotation仅存在于编译器处理期间,编译器处理完之后,该Annotation就没用了
*/
SOURCE,
/**
* 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期.
*/
CLASS,
/**
* 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,
* 保存到class对象中,可以通过反射来获取
*/
RUNTIME
}
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
2、@Documented和@Inherited
@Documented:标记注解,如javadoc此类的工具文档化。
@Inherited:标记注解,允许子类继承父类的注解
3、@Target
标明注解的修饰目标,共有
// ElementType取值
public enum ElementType {
/** 类、接口(包括注解类型)或枚举 */
TYPE,
/** field属性,也包括enum常量使用的注解 */
FIELD,
/** 方法 */
METHOD,
/** 参数 */
PARAMETER,
/** 构造函数 */
CONSTRUCTOR,
/** 局部变量 */
LOCAL_VARIABLE,
/** 注解上使用的元注解 */
ANNOTATION_TYPE,
/** 包 */
PACKAGE
}
三、@Retention使用场景:
1、RetentionPolicy.SOURCE
考虑我们平时会遇到的一种情况:
我们定义的类有一个 int 型的状态参数要设置,但我们设置的状态又只能限定在[TYPE_UNDERSCORE = 0, TYPE_BACKGROUND = 1]这两种状态,如果我们要提供一个接口来设置的话,那么一种做法是定义一个Enum枚举来作为参数,这样就能限定参数的取值范围了,但是使用枚举会比常量占用更多的内存。
这里可以用注解来处理这种问题,也就是下面要讲的自定义源码注解,这里需要用到一个元注解@IntDef,
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
@IntDef({PageTabSelectedType.TYPE_UNDERSCORE, PageTabSelectedType.TYPE_BACKGROUND})
public @interface PageTabSelectedType {
// 下划线选中模式
int TYPE_UNDERSCORE = 0;
// 背景选中模式
int TYPE_BACKGROUND = 1;
}
public void setSelectedType(@PageTabSelectedType int selectedType){
mSelectedType = selectedType;
}
这样在使用调用方法的使用只能使用指定的参数,就算用数值2,编译器也会提示报错。除了@IntDef注解外还用一个@StringDef注解可以使用,用来处理字符串。
2、RetentionPolicy.RUNTIME
一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,此时注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,可以通过反射的方式获取运行时注解。
/**
* 获取指定类型的注解
*/
public <A extends Annotation> A getAnnotation(Class<A> annotationType);
/**
* 获取所有注解,如果有的话
*/
public Annotation[] getAnnotations();
/**
* 获取所有注解,忽略继承的注解
*/
public Annotation[] getDeclaredAnnotations();
/**
* 指定注解是否存在该元素上,如果有则返回true,否则false
*/
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType);
/**
* 获取Method中参数的所有注解
*/
public Annotation[][] getParameterAnnotations();