2020-07-14 反射机制原理及使用
最近在研究蓝牙源码时发现,有部分类是被 @hide 掉的,例如 通过蓝牙获取通讯录及通话记录,是通过蓝牙 Pbap 协议,具体实现是在 BluetoothPbapClient 中实现,而该类是 hide 的,注释为当前未完成,后期变动可能会比较大,所以hide掉不会放在 sdk 里使用。
这种情况下,一般是通过修改源码重新编译来实现该需求,不过我为了快速完成需求,考虑使用反射来调用 BluetoothPbapClient 中的接口。
在此记录下反射的使用及原理。
反射,是能够在程序运行时修改程序的行为。
反射的使用
Class
获取 Class
Class 就是一个对象,它用来代表运行在 Java 虚拟机中的类和接口。
获取三种的方式:
student.getClass();
Student.class;
Class.forName("com.xx.Student");
获取 Class 修饰符
TestModifier.class.getModifiers()// 1025
Modifier.toString(TestModifier.class.getModifiers()))// public abstract
Field
获取 Filed
// 获取当前类所有的成员变量,包括私有或者非私有的,不包括从祖先类继承的非私有成员变量
getDeclaredFields();
getDeclaredField(String name);
// 获取所有当前类及从祖先类继承的非私有成员变量
getFields();
getField(String name);// 获取当前类所有的成员变量,包括私有或者非私有的,不包括从祖先类继承的非私有成员变量
getDeclaredFields();
getDeclaredField(String name);
// 获取所有当前类及从祖先类继承的非私有成员变量
getFields();
getField(String name);
获取 Field 的类型
// 该方法可以获取到泛型类型
getGenericType()
getType()
获取Field 修饰符
// 同 Class 获取修饰符
getModifiers()
Field 内容的读取与赋值
// get
publicObjectget(Object obj);
publicintgetInt(Object obj);
publiclonggetLong(Object obj)
throwsIllegalArgumentException, IllegalAccessException;
publicfloatgetFloat(Object obj)
throwsIllegalArgumentException, IllegalAccessException;
publicshortgetShort(Object obj)
throwsIllegalArgumentException, IllegalAccessException;
publicdoublegetDouble(Object obj)
throwsIllegalArgumentException, IllegalAccessException;
publicchargetChar(Object obj)
throwsIllegalArgumentException, IllegalAccessException;
publicbytegetByte(Object obj)
throwsIllegalArgumentException, IllegalAccessException;
publicbooleangetBoolean(Object obj)
throwsIllegalArgumentException, IllegalAccessException
// set
publicvoidset(Object obj, Object value);
publicvoidgetInt(Object obj,intvalue);
publicvoidgetLong(Object obj,longvalue)
throwsIllegalArgumentException, IllegalAccessException;
publicvoidgetFloat(Object obj,floatvalue)
throwsIllegalArgumentException, IllegalAccessException;
publicvoidgetShort(Object obj,shortvalue)
throwsIllegalArgumentException, IllegalAccessException;
publicvoidgetDouble(Object obj,doublevalue)
throwsIllegalArgumentException, IllegalAccessException;
publicvoidgetChar(Object obj,charvalue)
throwsIllegalArgumentException, IllegalAccessException;
publicvoidgetByte(Object obj,byteb)
throwsIllegalArgumentException, IllegalAccessException;
publicvoidgetBoolean(Object obj,booleanb)
throwsIllegalArgumentException, IllegalAccessException
注:在反射中访问了private修饰的成员,需要添加访问权限
fieldb.setAccessible(true);
Method
获取 Method
getDeclaredMethods()
getDeclaredMethod(String name, Class<?>... parameterTypes)
getMethods()
getMethod(String name, Class<?>... parameterTypes)
获取方法参数
publicParameter[] getParameters() {}
publicClass[] getParameterTypes() {}
publicType[] getGenericParameterTypes() {}
Parameter.java:
publicStringgetName(){}
publicClass getType() {}
publicintgetModifiers(){}
获取返回值类型
publicClass getReturnType() {}
// 获取返回值类型包括泛型
publicTypegetGenericReturnType(){}
获取修饰符
// 同 Class
publicintgetModifiers(){}
获取异常类型
publicClass[] getExceptionTypes() {}
publicType[] getGenericExceptionTypes() {}
方法执行
publicObjectinvoke(Object obj, Object... args){}
Constructor
获取 Constructor
注:Constructor 不能从父类继承,所以就没有办法通过 getConstructor() 获取到父类的 Constructor。
getDeclaredConstructors()
getDeclaredConstructor(Class<?>... parameterTypes)
getConstructors()
getConstructor(Class<?>... parameterTypes)
获取对象
在 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 文件