目录
1.注解介绍
注解(Annotation) 是在JDK5.0引入的一种注释机制,可以认为是对代码的一种标记,并不影响代码的执行,根据标记类型和内容,会在编译期给出错误提示,或者在运行期获取被标记的内容来进行下一步的操作
比如我们常用的@Override
就是一个编译期的注解,编译期会检查父类有没有同名的方法,否则会给出错误提示
2.注解的使用
注解可以通过@interface
关键字进行定义
public @interface Test{
int value() default -1;
}
这个就是一个最基本的注解定义,但这个注解并没有指定注解的元注解属性
3.元注解
元注解是可以注解到注解上的注解,元注解本身也是一种注解,但元注解可以作用于所有注解之上
目前的元注解有@Retention
,@Target
,@Documented
,@Inherited
,@Repeatable
这五种
3.1 @Retention
@Retention
英文意思是保留,就是指定注解的存活时间,目前有三种取值
1.RetentionPolicy.SOURCE
指定该注解只在源码阶段保留,在编译器进行编译的时候会被丢弃,比如我们常用的@Override
就是指定的这个属性,常用作编译器校验
2.RetentionPolicy.CLASS
指定该注解会被保留到编译期,也就是会被保留到字节码class文件中,但加载到JVM中会被丢弃;这个常用于编写自定义的注解框架,比如使用AbstractProcessor
配合javapoet
去捕获自定义标记,生成自定义的代码,如组件化框架Arouter
就是这种原理生成的
3.RetentionPolicy.RUNTIME
这个指定的存活时间最长,可以完整保留到JVM运行中,所以我们可以通过反射去捕获,比如EventBus
的早期实现就是注解加反射会执行相应的方法来实现事件总线和解耦的;这个也是我们自定义注解经常会用到的时间指定
3.2 @Target
@Target
英文释义为目标,也就是指定注解的作用域,哪些地方可以用
1.ElementType.TYPE
指定注解可以作用于类,接口或者枚举
2.ElementType.FIELD
指定注解可以作用于成员属性,butterknife
的BindView
使用的是这个
3.ElementType.METHOD
指定注解可以作用于方法,这个是用的最多的
4.ElementType.PARAMETER
指定注解可以作用于参数,retrofit
的@Field
等方法是这个方案,以及我们方法中常用的资源指定方案类型@IdRes
和DrawableRes
等也包含了这个属性,当然这两个不仅仅只能作为参数,方法和成员属性也是可以的
5.ElementType.CONSTRUCTOR
指定注解可以作用于构造方法
6.ElementType.ANNOTATION_TYPE
指定注解可以作用于注解,@Retention
和@Target
有这两个属性指定
剩下的LOCAL_VARIABLE
和PACKAGE
目前基本不会使用,分别是指定局部变量和包
3.3 @Documented
@Documented
英文释义为文档,这个注解的作用是将注解中的元素包含到javadoc
的文档中区,如果不使用javadoc等提取则无需关心
3.4 @Inherited
@Inherited
英文释义为继承,但这并不是指注解可以被继承,而是说如果一个父类被@Inherited
标记的注解进行了注解,那么他的子类就默认继承了该父类的注解,也就是说注解是默认不继承的
如
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Test {
int value();
}
@Test(value = 1234)
public static class TestParent {}
public static class TestChildren extends TestParent {}
public static void main(String[] args) {
TestParent testParent = new TestParent();
TestChildren testChildren = new TestChildren();
Test testAnnParent = testParent.getClass().getAnnotation(Test.class);
Test testAnnChildren = testChildren.getClass().getAnnotation(Test.class);
System.out.println(testAnnParent != null ? testAnnParent.value() : null);
System.out.println(testAnnChildren != null ? testAnnChildren.value() : null);
}
打印结果是
1234
null
也就是说子类并没有父类的注解信息,我再给注解添加@Inherited
标记
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Test {
int value();
}
这时的打印结果是
1234
1234
这时子类就可以拥有父类的注解属性了
3.5 @Repeatable
@Repeatable
英文释义为可重复,这个是JDK 1.8加进来的属性,通常一个注解在同一处是不能定义多次的,使用这个属性可以打破这个限制,但是写法有些限制,如下
@Repeatable(TestMultiply.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestSingle{
int value() default -1;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestMultiply{
TestSingle[] value();
}
@TestSingle(value = 2)
@TestSingle(value = 1)
public static class TestClass{}
public static void main(String[] args) {
TestClass testClass = new TestClass();
TestMultiply annotation = testClass.getClass().getAnnotation(TestMultiply.class);
TestSingle[] singles = annotation.value();
for (int i = 0; i < singles.length; i++) {
System.out.println("single ->>> "+singles[i].value());
}
}
注意这里的获取方法不再是获取上面的TestSingle,而是获取的是TestMultiply。也就是说,上面的注解可以等价为
@TestMultiply(value =
{@TestSingle(value = 1),@TestSingle(value = 2)})
public static class TestClass{}
4.注解的属性
注解的属性可以理解为类的成员变量,所有属性都以返回值的方法定义,此外,注解属性的返回值必须是8种基本数据类型,以及类,接口以及他们的数组,不可以使用自定义的类属性,属性可以有默认值,需用default
关键字指出,此外如果只有一个属性,一般默认会使用value()
方法
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
int id();
String msg();
...
}
@Test(id = 1234,msg = "1234")
public static class TestParent {
}
5.注解的获取
1.isAnnotationPresent
判断类,方法等是否被相应的注解所标记
2.getAnnotation
获取指定类型的注解,没有则返回空
3.getAnnotations
获取该类,方法等上所标记的所有注解信息
4.getParameterAnnotations
获取方法上的所有注解信息
常用的就这四种获取方法
然后写一个简单的测试案例,分别标记类,方法,成员变量以及参数
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value();
}
@Test(value = "类注解")
public static class TestClass {
@Test(value = "属性注解")
String field;
@Test(value = "方法注解")
public void method(@Test(value = "方法参数1") String value1, @Test(value = "方法参数2") String value2) {
}
}
public static void main(String[] args) {
try {
TestClass testClass = new TestClass();
Test classAnn = testClass.getClass().getAnnotation(Test.class);
Test fieldAnn = testClass.getClass().getDeclaredField("field").getAnnotation(Test.class);
Method method = testClass.getClass().getDeclaredMethod("method",String.class,String.class);
Test methodAnn = method.getAnnotation(Test.class);
Test[] paramAnns = new Test[2];
Annotation[][] paramAnnotions = method.getParameterAnnotations();
for (int i = 0; i < paramAnnotions.length; i++) {
Annotation[] annotations = paramAnnotions[i];
if (annotations[0] instanceof Test) {
paramAnns[i] = (Test) annotations[0];
continue;
}
}
System.out.println(classAnn.value());
System.out.println(fieldAnn.value());
System.out.println(methodAnn.value());
for (Test paramAnn : paramAnns) {
System.out.println(paramAnn.value());
}
} catch (Exception e) {
e.printStackTrace();
}
}
输出结果是
类注解
属性注解
方法注解
方法参数1
方法参数2
注意这里获取参数的方法getParameterAnnotations
返回的是二维数组,也就是说同一个参数是可以被多个注解所标记的,比如我们上面用到的注解修改一下
@Test(value = "方法注解")
public void method(@Test(value = "方法参数1") @TestSingle(value = 1234) String value1,
@Test(value = "方法参数2") String value2) {}
可以看出参数也是可以支持多注解的,这里用到的反射方法后面博客会讲到
6.@IntDef
说起注解就不得提一下@IntDef
和@StringDef
,我们在做类型判断时候经常会用到枚举,而枚举对内存并不是很友好;我们使用枚举主要是为了保持唯一性,调用时候不可以传其他的东西进来;而使用@IntDef
也可以做到编译的校验,即便是同一个int值,你不填写指定的引用会提示错误
@IntDef({Types.Type1, Types.Type2})
public @interface UserTypes {
}
public interface Types {
int Type1 = 1;
int Type2 = 2;
}
public static class TestClass {
public void setType(@UserTypes int type){
}
}
public static void main(String[] args) {
TestClass testClass = new TestClass();
testClass.setType(1); //编译器报警
testClass.setType(Types.Type1); //编译器通过
}
比如这个就是一个简单的@IntDef
的使用,在方法参数指定相应的标记;可以看出,同样是1,只有我们指定的值才能正常被编译器认可
@StringDef
使用方法也是如此,就不多赘述了