一,前期基础知识储备

反射

反射(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的动画,这种体验不好,需要优化。深入源码,

Android中的反射 反射获取activity_Java

可以看到setCurrentItem(position)方法最终是为了改变Viewpager中一个“mCurItem”的成员变量,该变量初始赋值是在setAdapter中,默认值为0。

Android中的反射 反射获取activity_Java_02

 所以,真正有效的改动应该是去修改“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中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接,因此灵活的运用它,能够使我们的代码更加灵活。但是反射它会使我们的软件的性能降低,复杂度增加,使用不当会成本很高。