注解
与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:
/**
- Created by Alex on 12/1/20
*/
public @interface PassengerType {
}
元注解:作用在注解上的注解
@Target
- ElementType.ANNOTATION_TYPE 可以应用于注解类型。
- ElementType.CONSTRUCTOR 可以应用于构造函数。
- ElementType.FIELD 可以应用于字段或属性。
- ElementType.LOCAL_VARIABLE 可以应用于局部变量。
- ElementType.METHOD 可以应用于方法级注解。
- ElementType.PACKAGE 可以应用于包声明。
- ElementType.PARAMETER 可以应用于方法的参数。
- ElementType.TYPE 可以应用于类的任何元素。
@Retention
- RetentionPolicy.SOURCE - 标记的注解仅保留在源码级别中,并被编译器忽略。作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。
- RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
- RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。
/**
* Created by Alex on 12/1/20
* 注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface PassengerType {
String value(); //无默认值
int age() default 1; //有默认值
}
RetentionPolicy.SOURCE
IDE语法检查
在Android开发中, support-annotations 与 androidx.annotation) 中均有提供 @IntDef 注解,此注解的定义如
下:
@Retention(SOURCE) //源码级别注解 @Target({ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}
Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。
比常量多5到10倍的内存占用。
此注解的意义在于能够取代枚举,实现如方法入参限制。
/**
* Created by Alex on 12/2/20
*/
public enum Passenger {
CHILD,ADULT
}
class PassengerTest{
public static void main(String[] args) {
setData(Passenger.CHILD);
}
public static void setData(Passenger p){
}
}
优化内存采用注解的方式,且带语法检查:
/**
* Created by Alex on 12/14/20
*/
@IntDef({IdType.IDCARD, IdType.PASSPORT,IdType.OHTERS})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER})
public @interface IdType {
/**
* 身份证
*/
int IDCARD = 1;
/**
* 护照
*/
int PASSPORT = 2;
/**
* 其他
*/
int OHTERS = 8;
//调用处
private fun getResult(ic:HSIDCardInfo,@IdType type:Int){
}
getResult(ic,IdType.IDCARD)
这个地方就必须是注解里定义的类型,随便传1、2、3的话编译会提示非法
}
APT注解处理器
APT全称为:“Anotation Processor Tools”,译为注解处理器。顾名思义,其用于处理注解。编写好的Java源文
件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工
具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac
调起,并将注解信息传递给注解处理器进行处理
RetentionPolicy.CLASS
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合
此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。
所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否
登录的判断。
//Java源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login { }
@Login
public void jumpA(){
startActivity(new Intent(this,AActivity.class));
}
public void jumpB(){
startActivity(new Intent(this,BActivity.class));
}
在上诉代码中, jumpA 方法需要具备登录身份。而 Login 注解的定义被设置为 CLASS 。因此我们能够在该类所编译的字节码中获得到方法注解 Login 。在操作字节码时,就能够根据方法是否具备该注解来修改class中该方法的内容加入 if-else 的代码段:(其实就是AOP切面)
//Class字节码
@Login
public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}
RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
反射
Java反射机制主要提供了以下功能
- 在运行时构造任意一个类的对象
- 在运行时获取或者修改任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法(属性)
获取Class对象的三种方式
- 通过类名获取 类名.class
- 通过对象获取 对象名.getClass()
- 通过全类名获取 Class.forName(全类名),classLoader.loadClass(全类名)
//使用 Class 类的 forName 静态方法
public static Class<?> forName(String className)
//直接获取某一个对象的 class
Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;
//调用某个对象的 getClass() 方法
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
创建实例
//使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class;
Object str = c.newInstance();
//先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
得到构造器的方法
Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)
获得字段信息的方法
Field getField(String name) -- 获得某public(且只能是public)字段 包括父类、父类的父类等
Field[] getFields() -- 获得类的所有public(且只能是public字段 包括父类、父类的父类等
Field getDeclaredField(String name) -- 获得某字段,包括私有,但不包括父类
Field[] getDeclaredFields() -- 获得类声明的所有字段,包括私有但不包括父类
获得方法信息的方法
//跟上面的获取属性同理
Method getMethod(String name, Class[] params)
Method[] getMethods()
Method getDeclaredMethod(String name, Class[] params)
Method[] getDeclaredMethods()