前言
在我们使用java开发Android项目的过程中,会发现大量的框架都使用了反射。反射机制允许我们在运行态中对任意一个类进行实例化、调用方法、得到或者设置变量值。这种动态获取信息以及动态调用对象方法的能力称为java语言的反射机制。反射是一种与类进行动态交互的机制,java虽然是静态语言,但却可以利用反射实现一些动态语言才有的功能(程序运行时,允许改变程序结构或者变量类型,这种语言被称为动态语言)。
在Android的框架层也能见到反射的身影,比如AMS启动Activity的核心就是使用反射机制,使用的Activity对象就是通过反射来构建的。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
最终调用instantiateActivity,通过cl.loadClass(className).newInstance()反射创建实例
public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
@Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity) cl.loadClass(className).newInstance();
}
反射能实现哪些功能
- 获取一个对象的类信息
- 在运行时构造任意一个类对象
- 在运行时判断任意一个对象所属的类
- 在运行时获取和调用任意一个类所具有的成员方法和变量
- 在运行时获取和调用任意一个对象的成员变量和方法
- 获取一个类的访问修饰符(public、private)、成员、方法、注解、超类等信息
- 获取属于一个接口的常量和方法声明
- 利用反射动态加载类,支持热修复和插件化
- 通过动态代理进行hook方法的执行
反射相关的主要类
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象就表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Field对象就表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法
得到class对象
什么是Class对象,如何获取
-
Class也是类,也是继承自Object
-
Class类对象不是new出来的,而是系统创建的。
传统方式new Cat() 使用classLoader加载
使用Class.forName(),仍然通过ClassLoader进行加载
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName(className, true, ClassLoader.getClassLoader(caller));
}
- 对于某个类的Class对象,在内存中只能有一份,因为类只加载一次
- 每个对象实例都会记得自己是由哪个Class实例所生成的
- 通过Class可以完整的得到一个类的完整的结构
- class对象是存放在堆上的
- 类的字节码二进制数据,是放在方法区的,也称为类的元数据(方法代码、变量名、方法名、访问权限等)
JVM在加载类的时候,会为每个类生成一个独一无二的class对象。
- Class.forName()---编译阶段获取
- 类名.class ---- 加载阶段获取
- 对象.getClass()--- 运行阶段获取
- classLoader.loadClass()---类加载器得到class对象
- 基本数据类型,可以通过
基本数据类型.class获取 - 基本数据类型的包装类,可以通过.type得到class类对象
包装类.TYPE
1.已知一个类的全类名,并且该类在类路径下,可通过class类的静态方法forName获取,多用于配置文件来读取类全路径,再加载类
Class.forName(String className);
2.若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高Class cls = A.class,多用于参数传递,比如通过反射得到对应构造器对象
Class c = Person.class;
3.已知某个类的实例,调用该实例的getClass方法获取class对象Class cls = 对象.getClass()
Person p = new Person();
Class clazz = p.getClass();
4.通过类加载器获取到类的Class对象
哪些类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface 接口
- 数据
- enum 枚举
- annotation 注解
- 基本数据类型
- void
通过反射获取类的结构信息
可以获取到哪些结构信息?
- getName---获取全类名
- getSimpleName---获取简单类名
- getFields---获取所有public修饰的属性,包含本类以及父类
- getDeclaredFields---获取本类中所有的属性
- getMethods---获取所有public修饰的方法,包含本类以及父类
- getDeclaredMethods---获取本类中所有的方法
- getConstructors---获取本类所有public修饰的构造器
- getDeclaredConstructors---获取本类中所有的构造器
- getPackage---以package形式返回包信息
- getSuperClass---以Class形式返回父类信息
- getInterfaces---以Class[]形式返回接口信息
- getAnnotations---以Annotation[]形式返回所有注解信息
获取Field信息
java.lang.reflect.Field
- getModifiers:以int形式返回修饰符(默认修饰符是0,public是1,private是2,protected是4,static是8,final是16)
- getType:以Class形式返回类型
- getName:返回属性名
获取Method
java.lang.reflect.Method类
- getModifiers:以int形式返回修饰符(默认修饰符是0,public是1,private是2,protected是4,static是8,final是16)
- getReturnType:以Class形式获取返回类型
- getName:返回方法名
- getParameterTypes:以Class[]返回参数类型数组
通过反射创建对象
创建的方式
- 方式1:调用类中的public修饰的无参构造器
- 获取类的Class对象
- 调用获取到的Class对象的newInstance()来获取对象,返回一个Object类型的对象,需要强转。
//获取user的Class对象
Class<?> userClass = Class.forName("fanshe.User");
//通过无参构造创建对象
User newInstance = (User)userClass.newInstance();
- 方式2:调用类中的指定构造器
- 获取类的Class对象
- 调用
getConstructor()获取一个Constructor对象 - 通过Constructor的newInstance()方法,创建对象
//获取user的Class对象
Class<?> userClass = Class.forName("fanshe.User");
//通过public有参构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
User newInstance2 = (User)constructor.newInstance("abc");
第一种方式只能使用默认的构造方法,不能通过带有参数的构造方法创建对象。第二种方式则可以通过指定参数的构造函数来创建对象。
通过反射修改属性Field
//拿到private的name的属性值
Field name = userClass.getDeclaredField("name");
//可以改变私有属性值
name.setAccessible(true);
name.set(obj, "zhangsan");
String n = (String)name.get(obj);
System.out.println(n);
通过反射调用方法Method
//1. 获取类中带有方法签名的方法,getMethod第一个参数为方法名,第二个参数为方法的参数类型数组
Method method = cls.getMethod("staticMethodName", new Class[] {double.class,String.class});
//invoke 方法的第一个参数是被调用的对象,这里静态方法传null,第二个参数为给将被调用的方法传入的参数
Object invoke = method.invoke(null, new Object[] {1.0,"abc"});
//如果方法是私有的private方法,按照上面的方法去调用则会产生异常NoSuchMethodException,这时必须改变其访问属性
//method.setAccessible(true);//私有的方法通过发射可以修改其访问权限
//2.非静态方法
Method method2 = cls.getMethod("methodName", new Class[] {double.class,String.class});
Object invoke2 = method2.invoke(new Cat(), new Object[] {2.0,"abcd"});
//3. 获取没有方法签名的方法print
Method method_3 = cls.getMethod("print", new Class[]{});
//result_3为null,该方法不返回结果
Object result_3 = method_3.invoke(new Cat(), null);
使用发射的注意事项
性能问题
在编译的时候代码都会被编译器优化一次,这种优化会让代码性能更好,减少更多的开销。而反射的性能问题主要来自没法享受到编译器做的优化,这点在移动设备上更为明显。除此之外,反射在使用中会遍历类的Field和Method,这种遍历本身就有性能损耗,而且传参和调用的时候也会有语言层面的拆箱和装箱,这也是性能损耗的原因之一。
- 优点:可以动态的创建和使用对象,使用灵活,没有反射机制,框架就失去低层支持
- 缺点:使用反射基本是解释执行,对执行速度有影响
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
m1();
m2();
}
//普通调用
public static void m1() {
Cat cat = new Cat();
long start = System.currentTimeMillis();
for(int i = 0;i<900000;i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("普通调用"+(end-start));
}
//反射调用
public static void m2() throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
Class cat = Class.forName("reflect.Cat");
Object newCat = cat.newInstance();
Method method = cat.getMethod("hi");
long start = System.currentTimeMillis();
for(int i = 0;i<900000;i++) {
method.invoke(newCat);
}
long end = System.currentTimeMillis();
System.out.println("反射调用"+(end-start));
}
普通调用4
反射调用26
优化:关闭访问检查
- Method和Field、Constructor对象都有setAccessible方法
- 作用是启动和禁用访问安全检查的开关
- 设置为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
结语
反射是一种非常实用的机制,在很多的框架中有广泛的使用,了解了反射的原理和API才能打开框架的大门,同样这也是Java/Android程序员必须要掌握的技能。接下来我还会深入反射的实际使用,比如动态代理的原理和解析。