为什么要使用反射?举个例子,程序在运行时接收到外部传入的一个对象,该对象的编译时类型是Object,但程序又需要调用该对象运行时类型的方法,编译时根本无法预知该对象和类可能属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,这时候就必须使用反射。
Java反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法,包括private修饰的方法;
Java反射所需要的类并不多,主要有java.lang.Class类,Java.lang.reflect包中的Field,Constructor,Method,Array类,这里简单挑几个介绍一下。
1.Class类
Class是Reflection反射机制的起源,想要操作类的属性和方法,就必须从获取Class 对象开始,获取Class对象途径:
1)使用类的class属性:
Class<java.util.Date> clz1 = java.util.Date.class;
2)通过Class类中的静态方法forName(String className),传入类的全限定名(必须添加完整包名)
Class<?> clz3 = Class.forName(“java.util.Date”);
3)通过对象的getClass方法来实现,其中,getClass()是Object类中的方法,所有的对象都可以调用该方法
java.util.Date str = new java.util.Date();
Class<?> clz2 = str.getClass();
2.Constructor类
Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
Class类获取构造器方法:
public Constructor<?>[] getConstructors():
该方法只能获取当前Class所表示类的public修饰的构造器
public Constructor<?>[] getDeclaredConstructors():
获取当前Class所表示类的所有的构造器,和访问权限无关
public Constructor<T> getConstructor(Class<?>... parameterTypes) :
获取当前Class所表示类中指定的一个public的构造器
参数:parameterTypes表示:构造器参数的Class类型
Constructor常用方法:
public T newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式.
参数:initargs:表示调用构造器的实际参数
返回:返回创建的实例,T表示Class所表示类的类型
如果一个类中的构造器是外界可以直接访问,同时没有参数.,那么可以直接使用Class类中的newInstance方法创建对象.
public Object newInstance():相当于new 类名();
调用私有的构造器:
访问私有的成员的时候,必须先设置可访问的:
对象.setAccessible(true);
调用私有构造器也一样,要先设置可访问:
构造器对象.setAccessible(true);
总的来说,使用反射创建对象的步骤如下:
1);找到构造器所在类的字节码对象,即上述的Class对象.
2):获取构造器对象.
3):使用反射,创建对象
3.Method类
使用反射获取类中的方法:
1):获取方法所在类的字节码对象.
2):获取方法.
Class类中获取Method的常用方法:
1)public Method[] getMethods():获取包括自身和继承过来的所有的public方法
2)public Method[] getDeclaredMethods():获取自身类中所有的方法(不包括继承的,和访问权限无关)
3)public Method getMethod(String methodName,Class<?>... parameterTypes):表示调用指定的一个公共的方法(包括继承的)
参数:
methodName: 表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型如String.class
4)public Method getDeclaredMethod(String name,Class<?>... parameterTypes):表示调用指定的一个本类中的方法(不包括继承的)
参数:
methodName: 表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型如String.class
使用反射调用方法的步骤:
1):获取方法所在类的字节码对象.
2):获取方法对象.
3):使用反射调用方法.
如何使用反射调用一个方法:
在Method类中有这样一个方法可以实现这个功能:
public Object invoke(Object obj,Object... args):表示调用当前Method所表示的方法
参数:
obj: 表示被调用方法底层所属对象
Method m = clz.getMethod("sayHi",String.class);
args:表示调用方法是传递的实际参数
返回:
底层方法的返回结果
如果是要调用私有方法,在调用之前要设置该方法为可访问的:
Method对象.setAccessible(true);
扩展:
1)使用反射调用静态方法:
静态方法不属于任何对象,静态方法属于类本身.
此时把invoke方法的第一个参数设置为null即可.
2)使用反射调用数组参数(可变参数):
调用方法的时候把实际参数统统作为Object数组的元素即可.
Method对象.invoke(方法底层所属对象,new Object[]{ 所有实参 });
class Student{
public static void doWork1(int... arr ){
System.out.println("doWork1方法被调用"+Arrays.toString(arr));
}
public static void doWork2(String...strings){
System.out.println("doWork2被调用"+Arrays.toString(strings));
}
}
//使用反射调用数组参数(可变参数):
public class MethodInvokeDemo {
public static void main(String[] args) throws Exception {
Class<?> clz1 = Student.class;
//情况1:数组的元素类型是基本类型
Method m = clz1.getMethod("doWork1", int[].class);
//m.invoke(null, 1,2,3,4,5);//错误
System.out.println(m);
//m.invoke(null, new int[]{1,2,3,4,5,6});//正确,也可以改成下面的
m.invoke(null, new Object[]{new int[]{1,2,3,4,5,6}});//正确
//情况2:数组的元素类型是引用类型
m = clz1.getMethod("doWork2",String[].class);
//m.invoke(clz1.newInstance(), new String[]{"A","b","C"});报下面的异常
//java.lang.IllegalArgumentException: wrong number of arguments
m.invoke(clz1.newInstance(), new Object[]{new String[]{"A","b","C"}});//正确
}
}