与声明一个'Class'不同,注解的声明使用@interface关键字。在注解里面可以定义变量,变量可以设置一个默认值,也可以不设置默认值。不设置默认值的时候需要在调用的时候给这个注解的变量塞值。
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Component {
int priority() default 0;
int a();
}
元注解就是注解上的注解。
@Target元注解标明我们的自定义注解是可以注解在类上面,方法上面还是变量上面。
@Retention元注解标明我们的自定义注解的保留级别,分别是SOURCE,CLASS,RUNTIME。
如果是SOURCE级别的,只会在源码阶段保留,javac编译成字节码后,自定义注解将会被抹除。
如果是CLASS级别的,即使编译成字节码,自定义注解依旧会在字节码中存在,但是JVM会忽略。
如果是RUNTIME级别的,表示可以在运行阶段让我们拿到这个注解,一般与反射技术一起使用,JVM中不会忽略。
这三个保留级别一层一层递进,后面的包含前面的。
根据注解的保留级别不同,对注解的使用自然存在不同场景。由注解的三个不同保留级别可知,注解作用于:源码、字节码与运行时。
我们的.java文件会被javac编译成.class文件,javac解析需要编译的java类的时候会采集所有的注解信息,把所有的注解信息包装成一个节点Element,再由javac调用注解处理程序,所以注解处理程序不需要手动调用,它是由javac调用的。
android提供的@DrawableRes注解:
我们看到下面的代码,定义了一个setDrawable方法,希望这个方法传入的是一个资源int,但是在实际调用的地方可能会乱传,造成各种问题。
public class Test {
//这里是想让调用的地方传一个资源的int
public static void setDrawable( int id){
}
public static void main(String[] args) {
//实际调用乱传
setDrawable(111);
}
}
针对这种情况,我们对setDrawable方法里面入参增加一个@DrawableRes注解:
可以看到,如果在实际调用的地方乱传的话,编译器会给一个提示 “Expected resource of type drawable”。但是实际上还是能编译通过的,只是提示一下而已。
android为我们提供了@IntDef注解:
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class IntDef(
/** Defines the allowed constants for this element */
vararg val value: Int = [],
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
val flag: Boolean = false,
/**
* Whether any other values are allowed. Normally this is
* not the case, but this allows you to specify a set of
* expected constants, which helps code completion in the IDE
* and documentation generation and so on, but without
* flagging compilation warnings if other values are specified.
*/
val open: Boolean = false
)
@IntDef的作用就是提供语法检查的,他是一个元注解,只能在注解上使用此注解:
public class Test {
//每一个成员就是一个WeekDay对象,耗内存
enum WeekDay{
SUNDAY,MONDAY
}
private static final int SUNDAY = 0;
private static final int MONDAY = 1;
public static void setCurrentDay(WeekDay currentDay){
}
//这里是希望传入的值在SUNDAY = 0或者MONDAY = 1里面取,但是实际上调用的地方会乱传值
public static void setCurrentDay(int currentDay){
}
public static void main(String[] args) {
setCurrentDay(SUNDAY);
//实际上调用的地方乱传值
setCurrentDay(100);
}
}
如果一个类里面存在枚举类,那么枚举类里面的每一个成员在生成的.class都会变成一个对象。如果枚举类里面成员非常多,就会生成非常多的对象,会耗费内存。我们可以使用常量代替枚举类。但是如果使用常量的话,真正函数调用的地方传入的值不一定是从0,1中取的,可以传入任何值。这时候就需要用@IntDef了。
我们先自定义一个@WeekDay注解:
public class Test {
@IntDef({SUNDAY, MONDAY})
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
@interface WeekDay{
}
private static final int SUNDAY = 0;
private static final int MONDAY = 1;
//这里是希望传入的值在SUNDAY = 0或者MONDAY = 1里面取,但是实际上调用的地方会乱传值
public static void setCurrentDay(@WeekDay int currentDay){}
public static void main(String[] args) {
setCurrentDay(SUNDAY);
setCurrentDay(MONDAY);
setCurrentDay(0);
setCurrentDay(10);
}
}
由于@IntDef注解是元注解,需要标注在注解上,所以我们定义一个@WeekDay注解,用@IntDef注解来注解@WeekDay注解,在@IntDef里面标识上允许传的值,然后对@WeekDay标识作用域和保留级。
现在我们可以看到,调用的地方只能传入SUNDAY和MONDAY啦,传入其他的会提示语法不合规。
用于字节码增强技术:在字节码里面写代码
在编译出Class后,通过修改class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。
.class文件有自己的格式,里面的数据按照特定的方式记录与排列。如果要直接修改字节码的话,需要把.class文件通过IO操作读成byte[],里面都是16进制的数字,不能乱改,需要按照规定格式来改,太困难太麻烦。
真正的.class文件如下:
可以看到都是16进制的数据,而我们在Android studio里面打开的.class文件都是反编译过的。
我们可以根据方法上是否有@InjectTime注解来决定是否加入特定的额外代码。
反射:在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。
一般情况下,我们使用某个类时必定知首它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用多个类对象进行操作。
反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。
getField方法:获得自己(不包括private)+父类的成员(不包括private)
getDecleredField方法:只能获得自己的成员(不包括父类)
如果要获取父类的私有变量,通过Class的getSuperclass方法先拿到父类,再调用getDecleredField方法拿到父类私有的变量。
通过getMethod方法获得类的方法的时候,执行method.invoke,第一个参数传递的是对象,第二哥是入参。调用filed的set方法的时候,第一个参数传递的也是对象,第二个传递的是值。