引子
Android开发的话一般都用过ButterKnife 、Retrofit之流,毕竟都是超流行的框架;
本想对这些东西做个使用小结,但是发现里面用的Java注解本人还挺模糊,所以先决定看一下Java注解这一块;
这文章适合对Java注解一窍不通的小伙伴一起学习;
Java注解Annotation
名词解释也不知道怎么说,先来看看样子。使用Android Studio开发的话,可以直接new一个java注解出来;
路径大概是:new->Java Class->kind中选择Annotation;
随便起个名字,比如MyAnnotation,点击OK就可以创建自己的注解了。
public @interface MyAnnotation {
}
这里可以看到,@interface这个标签,姑且就叫它标签好了;类似class代表类,interface代表接口一样,@interface这个标签代表注解;
下面先看一个真正的,有用的注解,ButterKnife中的BindView;
package butterknife;
import android.support.annotation.IdRes;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Bind a field to the view for the specified ID. The view will automatically be cast to the field
* type.
* <pre><code>
* {@literal @}BindView(R.id.title) TextView title;
* </code></pre>
*/
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
emm...看着很棒,除了@interface之外,还有这么多@XXX;这里先看下@Retention和@Target,这是在注解中的特殊存在——元注解;
元注解
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面;
这里先大概介绍几个元注解;
名称 | 使用 | 描述 |
---|---|---|
RetentionPolicy | @Retention | 保留策略,是一个枚举类型,用来决定保留多长时间 |
ElementType | @Target | 此枚举类型的常量提供了注释可能出现在Java程序中的语法位置的简单分类 |
Document | @Documented | 表示默认情况下,javadoc和类似工具将记录带有类型的注释。 此类型应用于注释类型的声明,其注释会影响客户端对带注释元素的使用。 如果使用Documented注释类型声明,则其注释将成为带注释元素的公共API的一部分。 |
Inherited | @Inherited | 表示自动继承注释类型。 如果注释类型声明中存在Inherited元注释,并且用户在类声明上查询注释类型,并且类声明没有此类型的注释,则将自动查询类的超类以获取注释类型。 将重复此过程,直到找到此类型的注释,或者到达类层次结构(对象)的顶部。 如果没有超类具有此类型的注释,则查询将指示相关类没有此类注释。 |
RetentionPolicy
分为3种策略:SOURCE、CLASS、RUNTIME,一个注解只能有一种策略;
- SOURCE:编译器将丢弃注释。也就是说注释只在编译器处理期间器作用,编译完就没用了;对应Java源文件(.java文件);
- CLASS:注释将由编译器记录在class文件中,但在运行时不需要由VM保留。 这是默认行为;对应.class文件;
- RUNTIME:注释将由编译器记录在class文件中,并在运行时由VM保留,因此可以反射性地读取它们;对应内存中的字节码;
ElementType
一共有10种类型,一个注解的类型可以有多个;
- TYPE:类、接口(包括注释类型)或枚举声明;
- FIELD:字段声明(包括枚举常量);
- METHOD:方法声明;
- PARAMETER:参数声明;
- CONSTRUCTOR:构造方法声明;
- LOCAL_VARIABLE:局部变量声明;
- ANNOTATION_TYPE:注释类型声明,元注解用的就是这个声明;
- PACKAGE:包声明;
在 Java 8 之前的版本中,只能允许在声明式前使用 Annotation。而在 Java 8 版本中,Annotation 可以被用在任何使用 Type 的地方,例如:初始化对象时 (new),对象类型转化时,使用 implements 表达式时,或者使用 throws 表达式时。
- TYPE_PARAMETER:JDK1.8新增, Type parameter declaration,表示这个 Annotation 可以用在 Type 的声明式前;
- TYPE_USE:JDK1.8新增,Use of a type,表示这个 Annotation 可以用在所有使用 Type 的地方(如:泛型,类型转换等);
再回头看BindView的代码,它使用的是@Retention(CLASS) @Target(FIELD),代表注释将记录在class中,注释的类型是字段,具体使用时如下:
@BindView(R.id.id_my_list)
RecyclerView myList;
注解的属性
在BindView的代码中,BindView还有一行代码@IdRes int value();这部分是注解的属性;
注解的属性是可选的,可以带属性,也可以不带任何属性;
无属性注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface MyAnnotation {
}
带属性注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface MyAnnotation {
String name() default "Michael Jordan";
int age();
}
属性是可以用default设置默认值的;
属性的赋值方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开;
@MyAnnotation(name = "GG", age = 10)
View btn1;
当然,带有default值的属性可以不被赋值,因此上面的代码写成如下形式,编译上依然不会有问题;
@MyAnnotation(age = 10)
View btn1;
但如果把age属性的赋值也去掉,那就出报错了;
还有一个特殊的属性value,当只对一个属性进行赋值的时候value可以省略不写;
public @interface MyAnnotation {
int value();
}
@MyAnnotation(10)
View btn1;
等同于
@MyAnnotation(value = 10)
View btn1;
这时候再回看ButterKnife中BindView的代码,它就是直接用的value做属性,所以在赋值时直接写对应的值就可以了;
注解的具体使用
SOURCE策略注解的使用
在控制输入参数时,我们常常用到枚举类型,这里可以用SOURCE类型的注释起到类似的效果;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
@IntDef({MyTestAnnotation.STATE_ON, MyTestAnnotation.STATE_OFF})
public @interface MyAnnotation {
}
public class MyTestAnnotation {
public static final int STATE_ON = 1;
public static final int STATE_OFF = 2;
int mState = STATE_OFF;
public void setState(@MyAnnotation() int state) {
mState = state;
}
public int getState() {
return mState;
}
}
可以成功限制输入的内容;
@IntDef也是一种元注解,它是属于android的,源码如下:
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
}
CLASS策略注解的使用
BufferKnife就是此类型的注解;
这里涉及到annotationProcessor和JavaPoet,相对比较复杂,这里有篇文章介绍相对详细:
https://www.jianshu.com/p/39fc66aa3297
RUNTIME策略注解的使用
这里通常是通过反射机制;
先定义两个注解,其中MyAnnotation包含MyInternalAnnotation;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyInternalAnnotation {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface MyAnnotation {
String name();
int age() default 0;
int[] arrayAttr() default {3, 6, 5};
MyInternalAnnotation annotationAttr() default @MyInternalAnnotation("Good");
}
再通过JAVA文件使用,千言万语都在代码中;
public class MyTestAnnotation {
String TAG = "mYtaG";
@MyAnnotation(name = "My Name")
String testString = "";
public void test() {
//检查是否存在注解
if (testString.getClass().isAnnotationPresent(MyAnnotation.class)) {
//获取注解
MyAnnotation annotation = testString.getClass().getAnnotation(MyAnnotation.class);
Log.v(TAG, "name:" +annotation.name());
Log.v(TAG, "age:" +annotation.age());
Log.v(TAG, "array-length:" +annotation.arrayAttr().length);
//获取子注解
MyInternalAnnotation internalAnnotation = annotation.annotationAttr();
Log.v(TAG, "internal value:" +internalAnnotation.value());
}
}
}
PS:属性不止可以用int和String还可以用枚举类型啥的;