文章内容尽量的详细,方便本身后续查阅。javascript
java反射是指,在运行状态中,对于任意一个类,都能知道这个类的全部属性及方法,对于任何一个对象,都能调用他的任何一个方法和属性,这种动态获取新的及动态调用对象的方法的功能叫作反射.java
Java中万物皆为对象,每一个类也是一个对象,每一个类的java文件在编译的时候会产生同名的.class文件,这个.class文件包含了这个java类的元数据信息,包括成员变量,属性,接口,方法等,生成.class文件的同时会产生其对应的Class对象,并放在.class文件的末尾。当咱们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,而后JVM再根据这个类型信息相关的Class对象建立咱们须要实例对象或者提供静态变量的引用值,在JVM中都只有一个Class对象,即在内存中每一个类有且只有一个相对应的Class对象。android
在Android 中,处于安全考虑,google会对系统的某些方法使用@hide或者使用Private修饰,致使咱们没办法正常调用此类方法,可是有些场景咱们又须要使用这些方法,这个时候就能够直接经过反射来调用修改。web
好比 Android 中 StorageManager
类中的 getVolumePaths()
方法,该方法为隐藏方法,没办法正常调用,可是在实际使用中咱们也可能用上,若是你有系统权限,那你就能够像 Android SD卡及U盘插拔状态监听及内容读取 这样随心所欲,若是没有权限,那你就能够经过反射来实现了,Demo放在最后,认真看完,你也会:grin:api
在Android9.0之后,Google对反射也作了限制,Google对反射的方法作了划分,并针对不一样的等级的隐藏方法作了反射限制。安全
白名单:SDK
浅灰名单:仍能够访问的非 SDK 函数/字段。
深灰名单:
对于目标 SDK 低于 API 级别 28 的应用,容许使用深灰名单接口。
对于目标 SDK 为 API 28 或更高级别的应用:行为与黑名单相同
黑名单:受限,不管目标 SDK 如何。 平台将表现为彷佛接口并不存在。 例如,不管应用什么时候尝试使用接口,平台都会引起 NoSuchMethodError/NoSuchFieldException,即便应用想要了解某个特殊类别的字段/函数名单,平台也不会包含接口。app名单列表ide
咱们先新建一个Person
类和Man
类,须要注意一下他们的继承关系及成员变量和方法的修饰符 (正常状况下不会这么写,我方便后面方法说明,就刻意的给了不一样的修饰符)函数
Person.java
:post
public class Person {
public int age;
private String name;
public Person() {
}
private Person(int age, String name) {
this.age = age;
this.name = name;
}
private Person(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
复制代码
Man.java
:
public class Man extends Person {
private String address;
public int phoneNum;
public Man() {
}
public Man(String address) {
this.address = address;
}
private Man(int phoneNum, String address) {
this.address = address;
this.phoneNum = phoneNum;
}
public String getAddress() {
return address;
}
private void setAddress(String address) {
this.address = address;
}
public int getPhoneNum() {
return phoneNum;
}
private void setPhoneNum(int phoneNum) {
this.phoneNum = phoneNum;
}
}
复制代码
前面说到过,java虚拟机(JVM)会根据这个类型信息相关的Class对象建立咱们须要实例对象或者提供静态变量的引用值,因此,咱们要用到反射,就首先要拿到这个类的Class对象,获取Class对象有下面三种方法
Class manClass = Man.class;
复制代码
该方法若是路径找不到会抛出
ClassNotFoundException
异常
try {
// forName 中要传入完整路径
manClass1 = Class.forName("com.evan.reflection.Man");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
复制代码
Man main = new Man();
Class<? extends Man> manClass2 = main.getClass();
复制代码
经过打印,能够看到三者获取结果一致。
拿到其Class对象,能够直接经过 newInstance()
方法,获取到类的实例对象,并对其操做
Man man = (Man) manClass.newInstance();
man.setPhoneNum(123);
int phoneNum = man.getPhoneNum();
System.out.println("getPhoneNum=" + phoneNum);
复制代码
结果:
你可能有疑惑,绕来绕去又绕回来了,干吗不直接new一个对象,非要绕一大圈,其实咱们这里最主要的是拿到其Class对象,而后用Class对象去执行私有方法或设置私有变量。
先贴结论,能够跟着后面的demo看结论,可能就会比较清晰。
构造方法只针对本类
方法 | 说明 |
---|---|
getConstructors() | 获取当前类公有构造方法 |
getConstructor(Class… parameterTypes) | 获取参数类型匹配的公有构造方法 |
getDeclaredConstructors() | 获取当前类全部构造方法,包括私有。 |
getDeclaredConstructor(Class… parameterTypes) | 获取全部参数类型匹配的构造方法(公有+私有) |
我这里贴心的再贴一下咱们写的Man
类的构造方法,能够看到一个公有的无参和一个公有的单参构造方法,一个私有的双参构造方法。
public Man() {
}
public Man(String address) {
this.address = address;
}
private Man(int phoneNum, String address) {
this.address = address;
this.phoneNum = phoneNum;
}
复制代码
获取当前类中全部用public
修饰的构造方法。
Constructor[] constructors = manClass.getConstructors();
for (Constructor c : constructors) {
System.out.println("getConstructors--" + c);
}
复制代码
结果:
从打印的结果能够看到,咱们拿到了一个无参的构造方法和一个String参数的构造方法,而这两个构造方法恰好是用Public
修饰的。
拿到该类的全部构造方法,无论修饰符是啥。
Constructor[] declaredConstructors = manClass.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
System.out.println("getDeclaredConstructors--" + c);
}
复制代码
结果:
不用说了吧,所有都拿到了!!
根据参数类型Class对象,只能获取指定公有构造方法。
try {
// getConstructor(String.class) 传入对应构造方法的参数类型Class对象
Constructor constructorPublic = manClass.getConstructor(String.class);
System.out.println("getConstructor(String.class)=" + constructorPublic);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
复制代码
结果:
该方法只能获取公有构造,若是咱们去获取私有的构造方法就会就会抛
NoSuchMethodException
异常。
获取指定参数Class对象的构造方法,无限制,获取公有或者私有均可以。
try {
constructorPublicDeclared = manClass.getDeclaredConstructor(String.class);
Constructor constructorPrivateDeclared2 = manClass.getDeclaredConstructor(int.class, String.class);
System.out.println("getDeclaredConstructor(String.class)--------" + constructorPublicDeclared);
System.out.println("getDeclaredConstructor(int.class, String.class)--------" + constructorPrivateDeclared2);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
复制代码
结果:
拿到了构造方法咱们直接调用 newInstance()
方法并传入对应参数能够拿到类对象。
try {
// 调用 newInstance 传入对应参数,获取Man实例并进行操做。
Man man = (Man) constructorPublicDeclared.newInstance("Test");
String address = man.getAddress();
System.out.println("newInstance address=" + address);
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 结果
newInstance address=Test
复制代码
Method 类
方法 | 说明 |
---|---|
getMethods() | 获取类自己及其父类全部公有方法 |
getMethod(String name, Class… parameterTypes) | 获取类自己及其父类经过方法名及参数类型指定的公有方法 |
getDeclaredMethods() | 获取类自己全部方法 |
getDeclaredMethod(String name, Class… parameterTypes) | 经过类自己经过方法名及参数类型获取本类指定的方法,无限制 |
获取当前类及其父类的Public
方法
Method[] methods = manClass.getMethods();
for (Method method : methods) {
System.out.println("getMethods--->" + method);
}
复制代码
结果:
能够看到获取的全是Public修饰的方法,不光获取了自身类,其父类Person类的公有方法同样打印出来了,大家可能会说Object类是什么鬼?Object类位于java.lang包中,是全部java类的父类。
获取本类的全部方法。
Method[] declaredMethods = manClass.getDeclaredMethods();
for (Method declareMethod : declaredMethods) {
System.out.println("getDeclaredMethods--->" + declareMethod);
}
复制代码
结果:
获取本类及父类指定方法名和参数Class对象的方法,好比获取其父类的setName方法和本身的getPhoneNum方法
public void setName(String name)
public int getPhoneNum()
try {
// 获取Person父类SetName方法
Method setName = manClass.getMethod("setName", String.class);
// 获取本身 getPhoneNum 方法
Method getPhoneNum = manClass.getMethod("getPhoneNum");
System.out.println("getMethod(String name, Class<?>... parameterTypes)--->" + setName);
System.out.println("getMethod(String name, Class<?>... parameterTypes)--->" + getPhoneNum);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
复制代码
结果:
若是你不信邪,去获取私有方法,会报错 NoSuchMethodException
获取本类指定方法名和参数Class对象的方法,无限制
try {
Method setAddress = manClass.getDeclaredMethod("setAddress", String.class);
Method getAddress = manClass.getDeclaredMethod("getAddress");
System.out.println("getDeclaredMethod--->" + setAddress);
System.out.println("getDeclaredMethod--->" + getAddress);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
复制代码
结果:
拿到方法后,方法调用使用:
Object invoke(Object obj, Object... args)
复制代码
就拿上面的例子
// 私有方法赋予权限
setAddress.setAccessible(true);
setAddress.invoke(manClass.newInstance(), "重庆市");
复制代码
setAccessible 当咱们须要对非公有方法进行操做的时候,须要先调用此方法赋予权限,否则也会抛异常
Field 类
方法 | 说明 |
---|---|
getFields() | 获取类自己及其父类全部公有成员变量 |
getField(String name) | 获取类自己及其父类指定的公有成员变量 |
getDeclaredFields() | 获取类自己全部成员变量(私有,公有,保护) |
getDeclaredField(String name) | 获取类自己指定名字的成员变量 |
获取本类及父类全部公有变量
Field[] fields = manClass.getFields();
for (Field field : fields) {
System.out.println("getFields--->" + field);
}
复制代码
结果:
获取本类全部成员变量
Field[] fields = manClass.getDeclaredFields();
for (Field field : fields) {
System.out.println("getDeclaredFields--->" + field);
}
复制代码
结果:
获取本类或者父类指定的成员变量
try {
Field age = manClass.getField("age");
Field phoneNum = manClass.getField("phoneNum");
System.out.println("getField--->" + age);
System.out.println("getField--->" + phoneNum);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
复制代码
结果:
获取本类指定的成员变量,无限制
try {
Field address = manClass.getDeclaredField("address");
Field phoneNum = manClass.getDeclaredField("phoneNum");
System.out.println("getField--->" + address);
System.out.println("getField--->" + phoneNum);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
复制代码
结果:
// 私有成员变量要赋予权限
address.setAccessible(true);
address.set(manClass.newInstance(), "重庆市");
phoneNum.set(manClass.newInstance(), 023);
复制代码
啰嗦这么久,直接拿文章开头说的那个方法动手,再看一下这个隐藏方法。
StorageManager.java ——> getVolumePaths
getVolumePaths()
: 返回所有存储卡路径, 包括已挂载的和未挂载的
虽然这个方法是 Public
修饰的,可是使用了@hide
注解,咱们直接使用 StorageManager
对象是找不到这个方法的,以下:
StorageManager sm = (StorageManager)
this.getSystemService(Context.STORAGE_SERVICE);
Class<? extends StorageManager> storageManagerClass = sm.getClass();
复制代码
// getVolumePaths 是用public修饰,因此这里getMethod和getDeclaredMethod均可以
// getVolumePaths 方法没有参数,能够不填
Method method = storageManagerClass.getMethod("getVolumePaths");
复制代码
若是方法非public
修饰,还须要 使用 setAccessible(true)
赋予权限
String[] paths = (String[]) method.invoke(sm, null);
复制代码
完整代码:
/**
* 反射调用 StorageManager ——> getVolumePaths 方法
*/
public void getVolumePaths() {
StorageManager sm = (StorageManager)
this.getSystemService(Context.STORAGE_SERVICE);
Class<? extends StorageManager> storageManagerClass = sm.getClass();
try {
// 反射拿到getVolumePaths方法
Method method =
storageManagerClass.getDeclaredMethod("getVolumePaths");
String[] paths = (String[]) method.invoke(sm, null);
for (String path : paths) {
Log.d(TAG, "getVolumePaths: path=" + path);
}
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
Log.e(TAG, "getVolumePaths: " + e.getLocalizedMessage(), e);
}
}
复制代码
结果:
其实咋一看,反射仍是很简单,主要区分私有和公有,以及获取的是本类的仍是本类加父类,固然,还有更多方法,好比你能够经过获取的方法,获取它的修饰符,变量等等,方法相似,本文内容仍是比较浅,敲一遍基本就知道是咋回事了,看的话可能有点绕,建议本身动手敲一遍,还有其其它获取反射中的知识点你也能够去拓展一下,反射在Android中使用仍是挺广,后续可能会说到热修复知识点,其中也涉及到反射知识。