1.反射介绍
反射(Reflection)是Java的高级特性之一,反射允许运行的程序获取当前已加载入JVM中的属性信息,并且可以操作部分属性变化,final
标记的属性无法通过反射修改,除了修改属性,反射也可以动态创建对象,并可以反射调用其相应的方法
android开发中,反射经常会配合DexClassLoader
一起使用,实现动态数据的更新,当我们要使用某个类或者某个方法,但并不确定该版本有没有相应的方法时,可以通过反射去进行校验,这个在DexClassLoader
加载的dex
文件中使用的比较多
2.反射的方法
Java中可以操作的反射基本有下面几种
1.Class
class类对象,这是反射的基础,反射都是基于类操作的
2.Field
类的成员变量
3.Method
类中定义的所有方法
4.Constructor
类的构造方法,通常用于反射实例化一个类对象使用
2.1 Class操作
class操作主要是用来获取相对应的class类型,以及后续的判定,也是反射的基础,没有类也就没法反射,主要有以下几种方式
1.Class.forName(String name)
传入类对应的全路径,需要捕获异常
Class<?> mClass =Class.forName("com.a.b.Test");
2.XXX.class
直接调用该类对应的.class
属性
Class<?> mClass = String.class
3.XXX.getClass()
调用该类实例的getClass
方法获取,和2不同的是这个是通过实例获取,第2个是用的类获取
Class<?> mClass = new String().getClass();
如果已经有一个类的实例,那么可以通过isInstance
方法判断是否是某个类的实例
boolean res = String.class.isInstance(new String("1234"));
2.2 Field操作
顾名思义,就是成员变量的反射操作,可以通过反射获取并修改类相应的属性(final标记的除外)
与Filed相关的方法有
1.getField(String name)
获取该类指定的名称的共有的成员属性
2.getFields()
获取该类所有的共有的成员属性
3.getDeclaredField(String name)
获取该类中指定的名称的定义的成员属性
4.getDeclaredFields()
获取该类中所有的定义的成员属性
5.set(Object obj, Object value)
设置该属性的值
getFileld
和getDeclaredField
的区别是前者只能获取类中所定义的public
属性,这个可以包含父类的public
; 而后者是获取当前类中定义的所有属性,包含public
,private
以及protected
的,但不包含父类的,如果想获取父类的则需要先通过getSuperclass
获取父类的类,然后再调用一次相应的方法
public static class TestParent {
public String strParent;
protected long longParent;
private int intParent;
}
public static class TestChild extends TestParent {
public String strChild;
protected long longChild;
private int intChild;
}
public static void main(String[] args) {
TestChild testChild = new TestChild();
Field[] fields = testChild.getClass().getFields();
Field[] decFields = testChild.getClass().getDeclaredFields();
testChild.getClass().getSuperclass()
for (Field field : fields) {
System.out.println("fields name ->>> "+field.getName());
}
for (Field decField : decFields) {
System.out.println("decFields name ->>> "+decField.getName());
}
}
我这里定义了一个父类和子类,都有独立的不同属性,打印结果是
fields name ->>> strChild
fields name ->>> strParent
decFields name ->>> strChild
decFields name ->>> longChild
decFields name ->>> intChild
可以看出getField
是可以获取父类的,而getDeclaredFields
是获取当前类所有的,不包含父类
获取到Field
属性后,就可以通过set(Object obj, Object value)
方法给相应的属性设置值,这里的第一个参数是需要设置的类的实例,如果是静态的变量这里传null就可以,第二个参数是我们要设置的属性值
比如上面我们可以用下面的方法修改值
public static void main(String[] args) {
TestChild testChild = new TestChild();
try {
Field field = testChild.getClass().getDeclaredField("strChild");
field.set(testChild,"1234");
System.out.println(testChild.strChild);
} catch (Exception e) {
e.printStackTrace();
}
}
这里就会输出1234
,也就是我们成功修改了相应实例中的属性
2.3 Method操作
这个是对方法的反射操作,相关的方法有
1.getMethod(String name, Class<?>... parameterTypes)
获取该类中指定名称的共有方法
,注意这里需要传入参数,因为有方法的重载,会有同名的方法但参数不同
2.getMethods()
获取该类中所有的共有方法
3.getDeclaredMethod(String name, Class<?>... parameterTypes)
获取该类中指定名称的定义的方法
4.getDeclaredMethods()
获取该类中所有的定义的方法
5.invoke(Object obj, Object... args)
调用相应的方法
public static class TestParent {
public void methodParent(String str, int value) {}
private void methodPrivateParent(){}
}
public static class TestChild extends TestParent {
private void methodChild(String str,int value){
System.out.println(str+":"+value);
}
private void methodPrivateChild(){}
}
public static void main(String[] args) {
TestChild testChild = new TestChild();
Method[] methods = testChild.getClass().getMethods();
Method[] decMethods = testChild.getClass().getDeclaredMethods();
for (Method method : methods) {
System.out.println("method ->>> "+method.getName());
}
for (Method decMethod : decMethods) {
System.out.println("decMethod ->>> "+decMethod.getName());
}
}
打印结果是
method ->>> methodParent
method ->>> wait
method ->>> wait
method ->>> wait
method ->>> equals
method ->>> toString
method ->>> hashCode
method ->>> getClass
method ->>> notify
method ->>> notifyAll
decMethod ->>> methodPrivateChild
decMethod ->>> methodChild
会发现,getMethods()
会把超类Object
中的共有方法也打印出来,getDeclaredMethods()
同样只打印自己类中的方法
当获取的Method
对象后,就可以通过invoke(Object obj, Object... args)
方法调用该方法,这里的第一个参数是该类的实例,同样如果是静态方法这里传null,后面的就是对应的参数属性了,需要按顺序填入,比如上面的方法我们可以这么反射
public static void main(String[] args) {
TestChild testChild = new TestChild();
try {
Method method = testChild.getClass().getDeclaredMethod("methodChild",String.class,int.class);
method.setAccessible(true);
method.invoke(testChild,"1234",666);
} catch (Exception e) {
e.printStackTrace();
}
}
注意这里的setAccessible
方法,当反射的属性或方法是私有的时候,需要调用这个方法去设置访问权限
2.4 Constructor操作
这是对构造方法的反射操作,也是我们反射创建实例最常用的方式
1.getConstructor(Class<?>... parameterTypes)
获取该类指定参数类型的共有的
构造方法
2.getConstructors()
获取该类所有的共有的
构造方法
3.getDeclaredConstructor(Class<?>... parameterTypes)
获该类指定参数的定义的
构造方法
4.getDeclaredConstructors()
获取该类所有的定义的
构造方法
5.newInstance(Object ... initargs)
通过构造方法创建类的实例
构造方法和上面方法的操作类型,比如我们通过上面的构造方法创建实例再反射调用相应的方法,可以写成
public static void main(String[] args) {
try {
Constructor<?> constructor = TestChild.class.getConstructor();
TestChild testChild = (TestChild) constructor.newInstance();
Method method = testChild.getClass().getDeclaredMethod("methodChild",String.class,int.class);
method.setAccessible(true);
method.invoke(testChild,"1234",666);
} catch (Exception e) {
e.printStackTrace();
}
}
3.反射的注意事项
- 反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射
- 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题