1.引言
从jdk1.5开始,引入了注解类Annotation,Annotation其实是一种接口,可以作用于类、方法、属性等,它可以通过反射机制来访问annotation信息,获取所加上注解信息,做相应的操作。相当于给相关的作用对象打上“tag”,使用方便,作用广泛。
2.java.lang中的注解
在java.lang中,用到的三种注解类,即常用到的Deprecated、Override和SuppressWarnings。
2.1@Deprecated过时
在使用AS编程的过程中,有一些方法写出来之后被画上了中划线,表示该方法已过时。@Deprecated注解起到的就是这个作用。
2.2@Override复写
这是开发过程中最常遇到的注解,表示复写父类中的方法。
2.3@SuppressWarnings阻止警告
阻止了弹出警告,比如屏蔽对上述过时的提示,可在调用
setOnPageChangeListener()
方法之上加上@SuppressWarnings("deprecation"),这样虽然在 IDE中仍以中划线形式表示,但如果使用javac命令就不再提示警告了。
3.元注解
那么以上的注解类的源码是什么样的呢,打开Override发现其代码如下:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这里可以发现注解类的定义方法以及注解类上所加的注解—元注解。元注解有四个,位于java.lang.annotation包中:Documented,Inherited,Retention,Target。
3.1@Target
表示注解作用的目的
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
注解可以作用的目标有枚举类ElementType决定,可作用于类、方法、属性等,数组表示注解可以有多个作用域。
public enum ElementType {
ANNOTATION_TYPE,//注解类
CONSTRUCTOR,//构造方法
FIELD,//属性
LOCAL_VARIABLE,//局部变量
METHOD,//方法
PACKAGE,//包
PARAMETER,//用于描述参数
TYPE,//类、接口、注解类型或枚举
TYPE_PARAMETER,
TYPE_USE;
private ElementType() {
}
}
注解作用于类时,定义@Target(ElementType.TYPE)ElementType.TYPE表示类型,Class,Interface等都实现了java中的Type接口,因此ElementType.TYPE表示注解作用于这些“类”(并不是单纯的Class类)
3.2@Retention
表示注解的作用时段
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Retention {
RetentionPolicy value();
}
有上面代码可见,Retention定义的是RetentionPolicy类型的数据,RetentionPolicy是一个枚举类型,包括SOURCE,CLASS,RENTIME三个类型,表示注解保留阶段。
SOURCE:表示注解只在源文件中保留。
CLASS:表示注解保留到.class文件中
RUNTIME:表示注解一直保留到内存中,类加载器把.class文件加载到内存中产生的字节码中要保留注解信息。
可想而知,@Override仅在写代码时用到,其Retention中的值为RetentionPolicy.SOURCE,@SuppressWarmings也只是编译器使用的,保留到SOURCE阶段。而@deprecated在内存中也需要保留,编译器需要得知方法是否过时,从加载到内存中的二进制文件中获取,因而要保留到RUNTIME阶段。
3.3@Documented
注解只是一个标记的话,那么使用javadoc生产文档时,这些注解都不会存在于文档中,要使注解在文档中也存在,就可以在使用了注解的作用对象上加上@Documented这个注解,javadoc所生产的文档就会带上注解信息。
3.4@Inherited
该注解表示子类可以集成加载父类上的注解,但是要注意:
1.注解定义在类上面,子类可以继承该注解
2.注解定义在方法上面,子类也可以继承该注解,但是如果子类复写了父类中定义了注解的方法,那么子类将无法继承该方法的注解,也就是说,子类在复写父类中被@Inherited标注的方法时,会将该方法上面的注解覆盖掉
3.Interface实现类无法继承接口中所定义的被@Inherited标注的注解
4.注解参数
在注解后为注解增加参数,如上述的@Retention、@Target后的括号中就是注解的参数。
参数的类型,支持基本数据类型,String类型,Class类型,enmu类型,Annotation类型以及上述所有类型的数据。
5.自定义注解
从上面的注解中,也大致看到了注解定义的方式。
1.使用@Interface定义一个注解类,其内部自行继承了Annotation类,
2.在该类中定义注解的参数,定义方式很像方法。
3.注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null,因此,使用空字符串或0作为默认值是一种常用做法。定义一些特殊的值,泪如空字符串或者负数,表示某个元素不存在参数设置默认值时,后面跟上default即可设置默认值。
4.当注解的参数只有一个时,建议用value作为参数名,这样在使用注解时,可以直接写参数的值接下来自定义一个注解类:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Car{
String name() default "";
int number() default -1;
}
在定义一个类去使用这个注解:@Car(name="好车",number=666)
public class CarBMW(){
}
那么如何获取这些注解值呢?有如下方法:if(CarBMW.class.isAnnotationPresent(Car.class)){//判断CarBMW类是否有注解类Car
Car carAnnotation=(Car)CarBMW.class.getAnnotation(Car.class);//获取注解实例对象
Log.v("Shawn",carAnnotation.name());//获取name参数的值
Log.v("Shawn",carAnnotation.number());//获取number参数的值
}
6.Android中的注解
Annotation就是注解了,JDK1.5新增加的功能,该功能可用于类,构造方法,成员变量,参数等的声明中,比如常见的@Override,butterknife库里的@Bind,该功能并不影响程序的运行,主要作用就是提供类型安全,对编译器警告灯辅助工具产生影响。
Android从API16引入了annotation包,最常见两个注解@TargetApi和@SuppressLint,
@TargetApi(Build.VERSION_CODES.XX)用于屏蔽某一新api中才能使用的方法报的lint检查出现的错误;
@SuppressLinet(“NewApi")屏蔽一切新api中才能使用的方法报的Android lint错误。
这两个注解的作用仅仅是屏蔽lint错误,再发方法中还要根据版本不同做不同的操作。
6.1Nullness注解
包括@NonNull和@Nullable,先来看@NonNull源码:
/**
- Denotes that a parameter, field or method return value can never be null.
- <p>
- This is a marker annotation and it has no specific attributes.
*/
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
public @interface NonNull {
}
Target中表示它可以应用于方法、参数、成员变量,使用@NonNull的好处就是避免后面频繁判断参数是否为空的操作。
6.2资源类型注解
此类注解以Res结尾,比如@BoolRes,@IdRes,@IntergerRes,@ColorRes等。
用法如下:
getDrawable(@DrawableRes int id)//getDrawable方法限定了传入的参数必须是Drawable资源文件
6.3@CallSuper
要求方法必须调用父类方法,可想而知,Activity的onCreate方法就有此注解标识。
6.4@idDef和@StringDef
提到最常用的Toast,在API21之前,一般可以这么写:
Toast.makeText(context, "msg", 0).show();
但是21之后,0突然被划红线了,查看源码发现,之前是这么写的:
- public static final int LENGTH_SHORT = 0;
- public static final int LENGTH_LONG = 1;
makeText方法中单纯的写着int duration参数,而21之后,成了这样: - @IntDef({LENGTH_SHORT, LENGTH_LONG})
- @Retention(RetentionPolicy.SOURCE)
- public @interface Duration {}
在makeText的duration参数之前加上了注解@Duration,这就要求传入的Duration中的值才不会报警报。@IntDef和@StringDef中放入了一个value数组,在开发中,这两个注解就可以限制限制枚举了。
6.4进程类注解
@UIThread,@BinderThread,@MainThread,@WorkerThread
用来限定方法或类在指定的线程类型中被调用
6.5权限注解@RequiresPermission
如果方法的调用需要权限,可以加这个注解,当需要几个权限中的一个时,使用anyOf,如果@RequiresPermission(anyOf= { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION})
若需要多个权限,使用allOf,如@RequiresPermission(allOf = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
若需要单独的标注读和写的权限访问,可以用@Read或者@Write标注每一个权限需求。
6.6@SdkConstant
表示一个常量字段应该被输出在SDK工具中使用,例如:
添加一个自定义的Action
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_MY_TEST = "android.intent.action.MY_TEST";
则可以这样使用Intent myTest = new Intent(Intent.ACTION_MY_TEST);
mContext.sendBroadcast(myTest);
6.7范围约束@FloatRange、@IntRange和@Size
前两个限制了数字的范围,比如可以用来限制传入参数值的范围,
@Size可以用来限定集合的大小或者限定字符串的长度,还可以做其他限制:1.限制最小最大数量:@Size(min=1,max=10)
2:数量必须是2的倍数(偶数):@Size(multiple=2)
6.8@CheckResult
该注解意味着需要对方法的返回值进行处理,例如:有个方法为:
- @CheckResult
- public boolean checkValid(String value){
return TextUtils.isEmpty(value);
- }
当调用者直接调用checkValid(”abc”)时会报错,需要对方法的返回值进行处理,无论返回true或false,需要进行后续操作,不能单单的调用一句。