个人博客
Java中的注解和反射
注解
Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制。
注解定义
通过@interface
来声明一个注解
public @interface Anno {
}
元注解
对注解进行注解的类就是元注解(meta-annotation),在自定义时,一般需要指定两个元注解
@Target
限制可以应用注解的Java元素类型,包括以下几种:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
-
TYPE:作用于类、接口或者枚举
-
FIELD:作用于字段
-
METHOD:作用于方法
-
PARAMETER:作用于方法参数
-
CONSTRUCTOR:作用于构造方法
-
LOCAL_VARIABLE:作用于局部变量
-
ANNOTATION_TYPE:作用于注解
@Retention
指定注解的保留阶段,有以下几种
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
-
SOURCE:注解只保留在源码中,编译时会被忽略。
-
CLASS:注解在编译时会保留,但JVM会忽略。
-
RUNTIME: 注解会被JVM保留,因此运行环境可以使用。
注解类型元素
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Anno {
//无默认值,在应用注解时必须设置
String value();
//有默认值
int age() default 18;
}
注解应用场景
根据Retention
的类型,注解的应用场景有以下三种:
SOURCE
作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。
IDE语法检查
Android中提供了@IntDef注解
@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
int[] value() default {};
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
}
这个注解的意义在于能够取代枚举,实现如方法入参限制。
如我们要限制参数只能在MONDAY
和TUESDAY
中的一个,可以先定义常量
public class WeekDay {
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
}
然后定义注解
@IntDef(value = {WeekDay.MONDAY, WeekDay.TUESDAY})
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
public @interface Week {
}
使用注解
public void test(@Week int week) {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test(WeekDay.MONDAY);
}
APT注解处理器
APT全称为:“Anotation Processor Tools”,意为注解处理器。编写好的Java源文
件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工
具,用来在编译时期扫描处理注解信息。
注解处理器是对注解应用最为广泛的场景。在Glide、EventBus、ButterKnife、ARouter等常用框架中都有注解处理器的身影。
CLASS
定义为CLASS的注解,会保留在class文件中,但是会被JVM忽略。应用场景为:字节码增强,通过修改字节码文件来达到修改代码执行逻辑的目的。常用的框架有:AspectJ、热修复Roubust。
RUNTIME
注解保留至运行期,我们可以通过反射获取注解中的所有信息。
反射
反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。
Class
Class是一个类,封装了当前对象所对应的类的信息。
获取Class对象
获取Class对象的三种方法
-
通过类名获取:类名.class
-
通过对象获取:对象名.getClass()
-
通过全类名获取:Class.forName(全类名) classLoader.loadClass(全类名)
创建实例
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class;
Object str = c.newInstance();
- 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这
种方法可以用指定的构造器构造类的实例
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("hello");
System.out.println(obj);
获取构造器信息
得到构造器的方法
Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的
一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs)
获取类的成员变量信息
Field getField(String name) -- 获得命名的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段
调用方法
获得方法信息的方法
Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法
当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。 invoke 方法的原型为:
public Object invoke(Object obj, Object... args)
利用反射创建数组
数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference 其中的Array类为
java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:
public static Object newInstance(Class<?> componentType, int length);
反射获取泛型真实类型
Type genType = object.getClass().getGenericSuperclass();
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
return (Class<?>) params[0];
基于注解和反射的简单应用
通常我们获取Intent传过来的extra,是通过这样的形式:
getIntent().getStringExtra("name");
getIntent().getIntExtra("age",18);
现在,我们通过注解和反射实现自动获取Extra,类似这样:
@InjectExtra
private String name;
@InjectExtra("age")
private int age;
@InjectExtra("gender")
private boolean gender;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectHelper.inject(this);
Log.i(TAG, "name:" + name + ",age:" + age + ",gender:" + gender);
}
实现步骤
- 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectExtra {
String value() default "";
}
- 定义InjectHelper
public class InjectHelper {
/**
* 注入Extra
*
* @param activity
*/
public static void inject(Activity activity) {
try {
Class<? extends Activity> clz = activity.getClass();
Field[] declaredFields = clz.getDeclaredFields();
for (Field field : declaredFields) {
boolean annotationPresent = field.isAnnotationPresent(InjectExtra.class);
if (annotationPresent) {
InjectExtra annotation = field.getAnnotation(InjectExtra.class);
String name = annotation.value();
if (TextUtils.isEmpty(name)) {
//如果注解没有指定value,就用字段名
name = field.getName();
}
Object object = activity.getIntent().getExtras().get(name);
field.setAccessible(true);
field.set(activity, object);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 使用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("name", "zs");
intent.putExtra("age", 18);
intent.putExtra("gender", true);
startActivity(intent);
}
}
public class TargetActivity extends AppCompatActivity {
private static final String TAG = InjectHelper.class.getSimpleName();
@InjectExtra
private String name;
@InjectExtra("age")
private int age;
@InjectExtra("gender")
private boolean gender;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectHelper.inject(this);
Log.i(TAG, "name:" + name + ",age:" + age + ",gender:" + gender);
}
}
- 输出结果
2020-04-27 10:38:22.495 19687-19687/com.wangyz.annotation I/InjectHelper: name:zs,age:18,gender:true
这里为了演示注解与反射,指定Retentaion为RUNTIME,实际上可以指定为SOURCE级别,通过APT来生成辅助类,来减少手动获取Extra的工作量。