一、什么是反射机制?为什么要用反射机制?
所谓Java反射机制是指,程序在运行状态时,可以加载一个运行时才得知名称的class,能够知道这个类的所有属性和方法,并生成其对象实体、或对其fields设值、或调用其方法;即利用反射技术,根据一个类名称,可以得到该类的构造方法、属性、方法等信息,并创建其对象。用一句话来概括,反射就是加载一个运行时才知道的类以及它的完整内部结构。
那我们为什么要用反射机制呢?
第一,反射的目的就是为了扩展未知的应用。比如,我们写好了一个软件,其中定义了一些接口,程序已经过编译并且发布了,当我们以后需要扩展功能时,不可能去修改已经安装在别人机器上的软件源码,此时我们只需要另写一个插件,让其实现某些接口即可,程序运行时,通过反射技术动态的创建和编译新写的类,并获知其内部细节,就可以调用其方法了;
第二,在编码阶段不知道那个类名,要在运行期从配置文件读取类名, 这时候就没有办法以new的方式硬编码,而必须用到反射才能创建这个对象。
二、如何使用反射?
1. 反射中的类
Java反射机制的实现要借助于4个类:Class、Constructor、Field、Method,其中,
Class——类对象(Class 类的实例表示正在运行的 Java 应用程序中的类和接口 )
Constructor——类的构造器对象
Field——类的属性对象
Method——类的方法对象
可以看到一个类的各个组成部分(构造方法、属性、方法)都被封装成一个单独的类。
而java.lang.Class 提供了四种独立的反射调用,以不同的方式来获得以上信息。
获取构造方法的反射调用:
Constructor getConstructor(Class[] params) -- 匹配给定的参数类型来得到相应的公共构造函数
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 匹配给定的参数类型来得到相应 的构造函数 (从public、private、protect、默认中匹配选择)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数
获取字段属性的反射调用:
Field getField(String name) -- 获得匹配给定名称参数的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得匹配给定名称参数的字段(从public、private、protect、默认中匹配选择)
Field[] getDeclaredFields() -- 获得类声明的所有字段
获取方法的反射调用:
Method getMethod(String name, Class[] params) -- 匹配给定的方法参数名name和参数 类型param的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 匹配给定的方法参数名 name和参数类型param的方法(从public、private、protect、默认中匹配选择)
Method[] getDeclaredMethods() -- 获得类声明的所有方法
2. 反射的使用步骤
步骤一:获取类的Class对象和创建类的实例对象
· 获取类的Class对象:
① 通过Class.forName(String className) ——className表示完整的类路径
② 运用.class语法,如String.class
③ 调用Object的getClass()方法,如:
String str = "abc";
Class c = str.getClass();
④ 对于基本数据类型,可以使用其包装类中与定义好的TYPE字段,如
Class c = Integer.TYPE;
其中,以“.class”来创建Class对象的引用时,不会自动地初始化该Class对象,而使用Class.forName()立即就进行了初始化。例如,如果一个static final属性值是“编译期常量”(public static final int A = 2;),那么这个值不需要对类进行初始化就可以被读取。
注:获取Class的引用,其实就是在加载或寻找编译期编译出来的“.class”字节码文件。
附(
Class.forName() 和 ClassLoader.loadClass()的区别
Class.forName("xx")等同于 Class.forName("xx",true,CALLClass.class.getClassLoader()),第二个参数(bool)表示 装载类的时候是否初始化该类,即调用类的静态块的语句及初始化静态成员变量并为其分配空间。
ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也可以用 (ClassLoader.getSystemClassLoader())
Class cls = loader.loadClass("xx"); //这句话没有执行初始化,其实与 Class.forName("xx.xx",false,loader)是一致的,只是loader.loadClass("xx")执行的 是更底层的操作。
只有执行cls.NewInstance()才能够初始化类,得到该类的一个实例
)
· 创建类的实例对象,如:
Class c = Class.forName("com.ReflectionTest.");
Object obj = c.newInstance();
步骤二:调用诸如getDeclaredMethods()的方法,已取得类的构造方法、属性或者方法
例如:
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
步骤三:对于第二步骤取得的属性、方法、构造方法等采用Reflection API进行处理
下面引用一个例子:
/**
* 通过java的反射机制动态修改对象的属性
* @param o
*/
public void test2(Customer o) {
try {
Class c = o.getClass();
//getMethod方法第一个参数指定一个需要调用的方法名称,第二个参数是需要调用方法的参数类型列表,如无参数可以指定null,该方法返回一个方法对象
Method sAge = c.getMethod("setAge", new Class[] { int.class });
Method gAge = c.getMethod("getAge", null);
Method sName = c.getMethod("setName", new Class[] { String.class });