最近在研究蓝牙源码时发现,有部分类是被 @hide 掉的,例如 通过蓝牙获取通讯录及通话记录,是通过蓝牙 Pbap 协议,具体实现是在 BluetoothPbapClient 中实现,而该类是 hide 的,注释为当前未完成,后期变动可能会比较大,所以hide掉不会放在 sdk 里使用。
这种情况下,一般是通过修改源码重新编译来实现该需求,不过我为了快速完成需求,考虑使用反射来调用 BluetoothPbapClient 中的接口。
在此记录下反射的使用及原理。
反射,是能够在程序运行时修改程序的行为。
反射的使用
Class
获取 Class
Class 就是一个对象,它用来代表运行在 Java 虚拟机中的类和接口。
获取三种的方式:
1 | student.getClass(); |
获取 Class 修饰符
1 | TestModifier.class.getModifiers() // 1025 |
Field
获取 Filed
1 | // 获取当前类所有的成员变量,包括私有或者非私有的,不包括从祖先类继承的非私有成员变量 |
获取 Field 的类型
1 | // 该方法可以获取到泛型类型 |
获取Field 修饰符
1 | // 同 Class 获取修饰符 |
Field 内容的读取与赋值
1 | // get |
注:在反射中访问了private修饰的成员,需要添加访问权限:
1 | fieldb.setAccessible(true); |
Method
获取 Method
1 | getDeclaredMethods() |
获取方法参数
1 | public Parameter[] getParameters() {} |
获取返回值类型
1 | public Class<?> getReturnType() {} |
获取修饰符
1 | // 同 Class |
获取异常类型
1 | public Class<?>[] getExceptionTypes() {} |
方法执行
1 | public Object invoke(Object obj, Object... args) {} |
Constructor
获取 Constructor
注:Constructor 不能从父类继承,所以就没有办法通过 getConstructor() 获取到父类的 Constructor。
1 | getDeclaredConstructors() |
获取对象
在 Java 反射机制中有两种方法可以用来创建类的对象实例:Class.newInstance() 和 Constructor.newInstance()。官方文档建议开发者使用后面这种方法,下面是原因。
Class.newInstance() 只能调用无参的构造方法,而 Constructor.newInstance() 则可以调用任意的构造方法。
Class.newInstance() 通过构造方法直接抛出异常,而 Constructor.newInstance() 会把抛出来的异常包装到 InvocationTargetException 里面去,这个和 Method 行为一致。
Class.newInstance() 要求构造方法能够被访问,而 Constructor.newInstance() 却能够访问 private 修饰的构造器。
反射的原理
java让我们在运行时识别对象和类的信息,主要有2种方式:一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息,另一种是反射机制。
它允许我们在运行时发现和使用类的信息,也就是这里要说的,通过 Class 类与 reflect 类库一起对反射进行支持,该类库包括 Field、Method 和 Constructor 类。这些类的对象由JVM在启动时创建,用以表示未知类里对应的成员。这样可以使用 Contructor 创建新的对象,用 get() 和 set() 方法获取、修改类中成员等,用 invoke() 调用 Method 对象关联的方法等等。
这样对象信息可以在运行时完全确定下来,而在编译时不需要知道任何关于类的信息。
反射机制并没有很神奇之处,当通过反射对一个未知类型的对象做处理时,JVM只是简单的检查这个对象属于哪个特定的类,因此那个类的 .class 对于JVM是肯定可以获取的。
综上,对于 RTTI 和 反射的区别只在于:
- RTTI 是编译器在编译时打开和检查 .class 文件
- 反射 是在运行时打开和检查 .class 文件