前言
欢迎大家我分享和推荐好用的代码段~~
声明
欢迎转载,但请保留文章原始出处:
CSDN:http://www.csdn.net
雨季o莫忧离:http://blog.csdn.net/luckkof
正文
一、什么是反射机制?为什么要用反射机制?
所谓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 });
//动态修改Customer对象的age
Object[] args1 = { new Integer(25) };
sAge.invoke(o, args1);
//动态取得Customer对象的age
Integer AGE = (Integer) gAge.invoke(o, null);
System.out.println("the Customer age is: " + AGE.intValue());
//动态修改Customer对象的name
Object[] args2 = { new String("李四") };
sName.invoke(o, args2);
} catch (Throwable e) {
System.err.println(e);
}
}
3.反射机制的功能?
① 在运行时查询并调用任意一个类所具有的成员变量和方法;
② 在运行时判断任意一个对象所属的类。例如:
String str = new String("abc");
Class c = str.getClass();
Boolean flag = c.isInstance(new String())
③ 在运行时创建类的实例;
·运行时创建实例对象
有两种办法生成实例对象:
针对无参数的构造方法,可以使用Class类里的newInstance()方法,该newInstance()方法不带参。例如:
Class c = String.class;
Object obj = c.newInstance();
要调用有参数的构造方法,就需要通过Constructor类的newInstance(Object ... obj)方法带参。例如:
//获得String的Class实例
Class clazz=String.class;
//创建一个数组,这个数组用来放要实例化对象的构造器里的参数类型
Class[] param=new Class[1];
//放入构造器中的参数类型,如果有多个,按顺序放入
param[0]=String.class;
//实例化一个构造器对象,并把放着构造器参数类型的数组作为参数传进去
Constructor constructor=clazz.getConstructor(param);
//创建一个Object数组,用于放构造器中对应的值
Object[] obj=new Object[1];
//将值放入到数组中,这里要注意,param数组中放入的是什么类型,这里就要按顺序放入
obj[0]="zhang3";
//实例化对象,并把放着构造器要传入的参数的数组传到方法中
String str=(String)constructor.newInstance(obj)
这样,我们就通过java.lang.reflect.Constructor实例化出来了String类型的对象。
·newInstance()和new关键字创建实例的比较
在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个 是方法,一个是关键字外,最主要的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。那么为什么会有两种创建对象方式?这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想。
从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:
1、这个类已经加载;
2、这个类已经链接了(即为静态域分配存储空间,并且如果必须的话将解析这个类创建的对其他类的所有引用)。而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载javaAPI的那个加载器。
可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。+这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。
最后用最简单的描述来区分new关键字和newInstance()方法的区别: newInstance:弱类型。低效率。只能调用无参构造方法。
New:强类型。相对高效。能调用任何public构造。
jvm会执行静态代码段,只要记住一个概念,静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了。而且以后不会再走这段静态代码了。
④ 当要调用的一个类的方法为private时,可以采用反射技术调用;
先根据完整类名加载该类——>利用newInstance或Constructor实例化——>通过getDeclaredMethods()得到私有的方法——>取消Java语言访问检查以访问private方法(即调用setAccessible(true)) ——>最后通过Method的invoke方法调用该私有方法。
⑤ 利用反射实现类的动态加载(实现java动态绑定的前提)
多态是Java面向对象语言的一个重要特性,而向上转型是实现多态的重要一步,对于面向接口或父类的引用,JVM是靠后期绑定(动态绑定)的机制进行对象类型的判定的,即在运行时才能确定调用哪些代码。而实现动态绑定的基础便是反射机制的应用,通过反射在运行时根据类名得到该类的具体信息。
三、我们学习过的知识中哪些用了反射机制?
工厂模式、Spring中IOC(或DI)、AOP都使用到了java反射机制