一,前期基础知识储备
反射
反射(Reflection)是程序在运行状态中动态检测,访问或者修改类型的行为,具体表现为以下两个方面:
- 对于任意一个类,都知道这个类的所有属性和方法。
- 对于任意一个对象,都能够调用它的任何一个方法和属性。
反射可以让我们在运行时获取类的属性和方法,构造方法,父类,接口等信息,还可以让我们在运行期实例化对象和调用方法等。例如,
// 获取系统属性
public static String getSystemProperty(String key) {
try {
Class<?> clsSystemProperties = Class.forName("android.os.SystemProperties");
Method methodGet = clsSystemProperties.getDeclaredMethod("get", String.class);
Object result = methodGet.invoke(clsSystemProperties, key);
return result == null ? null : result.toString();
} catch (Exception e) {
return null;
}
}
在运行时获取类
编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。在Java中也就是把Java代码编成class文件的过程.编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
运行期:是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来,在Java中把磁盘中的代码放到内存中就是类加载过程,类加载是运行期的开始部分。
如何使用
想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象。
反射获取类
Class<?> cls1 = XXClass.class //类获取
Class<?> cls2 = XXObject.getClass() //类对象获取
Class<?> cls3 = Class.forName("com.xx.XXClass") //类路径获取
反射方法
getName // 类的完整名字
newInstance() //返回此Class所表示的类,通过调用默认的(即无参数)构造函数创建的一个新实例
getFields() //获取这个类中所有被public修饰的成员变量
getField(String name) //获取指定名字的被public修饰的成员变量
getDeclaredFields() //获取这个类中的所有属性
getMethods() //获取这个类中public类型的方法
getDeclaredMethods() //获取这个类中的所有方法
getDeclaredMethod(String name, Class… parameterTypes) // 获取指定的构造方法(参数:参数类型.class)
getDeclaredConstructors() // 获取所有的构造方法
getReturnType() //获取方法的返回类型
getParameterTypes() //获取方法的传入参数类型
getSuperclass() //获取这个类的父类
getInterfaces()// 获取这个类实现的所有接口
总结:
获取构造函数、成员变量、成员方法都有一个获取所有的方法,根据需要去调用。如:getConstructor:获取单个构造函数,getConstructors:获取所有构造函数。
没有 Declared 前缀会返回所有的 public 方法,包括父类的方法。
而加 Declared 前缀会返回所有自己定义的方法,public,protected,private 都在此,但是不包括父类的方法。
二,上代码,具体调用
实际开发中遇到一个需求“为Viewpager指定新的默认页”,要是直接使用setCurrentItem(position),可以达到需求,但是会闪一下屏,可以看到一个切换tab的动画,这种体验不好,需要优化。深入源码,
可以看到setCurrentItem(position)方法最终是为了改变Viewpager中一个“mCurItem”的成员变量,该变量初始赋值是在setAdapter中,默认值为0。
所以,真正有效的改动应该是去修改“mCurItem”的默认值,此处就可以使用反射去处理。代码如下:
/*使用反射 设置ViewPager首选页 要可以跳过动画*/
private void setDefaultItem(int position){
try {
Class c = Class.forName("android.support.v4.view.ViewPager");
Field field =c.getDeclaredField("mCurItem");
// 参数值为true,打开禁用访问控制检查
// setAccessible(true) 并不是将方法的访问权限改成了public,而是取消java的权限控制检查。
field.setAccessible(true);
field.setInt(mVpPhoto, position);
} catch (Exception e) {
e.printStackTrace();
}
mPhotoPagerAdapter.notifyDataSetChanged();
}
1)调用Class.forName通过类路径获取类;
2)调用getDeclaredField()方法获取该对象的变量;
3)调用setAccessible(),打开禁用访问控制检查,通过set(Object obj,value)重新设置新的属性值,并且当我们需要获取私有属性的属性值得时候,我们必须设置Accessible为true,然后才能获取;
一般来说,构造函数大部分是 public ,而成员变量和方法大部分是 private。public 权限直接正常调用即可,private需要在调用前增加 setAccessible(true)
4)调用Java Field.set(),向对象的这个Field属性设置新值value。将指定对象变量上此 Field 对象表示的字段设置为指定的新值.。
public native void setInt(Object obj, int i)
throws IllegalArgumentException, IllegalAccessException;
参数:此方法接受接受两个参数:
obj:是应该修改其字段的对象;
i:这是要修改的obj字段的新值。
三,反射利弊
利
运行时类型的判断
动态类加载,动态代理使用反射。
弊
性能问题,反射相当于一系列解释操作。
JVM 不会去优化这部分代码
代码不易阅读
为什么会出现性能问题?
invoke 方法会对参数做封装和解封操作
需要检查方法可见性
需要校验参数
反射方法难以内联
JVM 无法优化: Java 文档中介绍
反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接,因此灵活的运用它,能够使我们的代码更加灵活。但是反射它会使我们的软件的性能降低,复杂度增加,使用不当会成本很高。