java使用反射创建并操作对象的方法
Class对象可以获得该类里的方法(由Method对象表示)、构造器(由Constructor对象表示)、成员变量(由Field对象表示),这三个类都位于java.lang.reflect包下,并实现了java.lang.reflect.Member接口。程序可以通过对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的成员变量值。
创建对象
通过反射来生成对象需要先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。
在很多JavaEE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据该字符串来创建对应的实例,就必须使用反射。
下面程序就实现了一个简单的对象池,该对象池会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入一个HashMap中。
publicclassObjectPoolFactory{
//定义一个对象池,前面是对象名,后面是实际对象
privateMapobjectPool=newHashMap<>();
//定义一个创建对象的方法
//该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
privateObjectcreateObject(StringclazzName)throwsException,IllegalAccessException,ClassNotFoundException{
//根据字符串来获取对应的Class对象
Class>clazz=Class.forName(clazzName);
//使用clazz对应类的默认构造器创建实例
returnclazz.getConstructor().newInstance();
}
//该方法根据指定文件来初始化对象池
//它会根据配置文件来创建对象
publicvoidinitPool(StringfileName)
throwsInstantiationException,IllegalAccessException,ClassNotFoundException{
try(FileInputStreamfis=newFileInputStream(fileName)){
Propertiesprops=newProperties();
props.load(fis);
for(Stringname:props.stringPropertyNames()){
//每取出一对key-value对,就根据value创建一个对象
//调用createObject()创建对象,并将对象添加到对象池中
objectPool.put(name,createObject(props.getProperty(name)));
}
}catch(Exceptionex){
System.out.println("读取"+fileName+"异常");
}
}
publicObjectgetObject(Stringname){
//从objectPool中取出指定name对应的对象
returnobjectPool.get(name);
}
publicstaticvoidmain(String[]args)throwsException{
ObjectPoolFactorypf=newObjectPoolFactory();
pf.initPool("obj.txt");
System.out.println(pf.getObject("a"));//①
System.out.println(pf.getObject("b"));//②
}
}
上面程序中createObject()方法里的两行粗体字代码就是根据字符串来创建Java对象的关键代码,程序调用Class对象的newInstance()方法即可创建一个Java对象。程序中的initPool()方法会读取属性文件,对属性文件中每个key-value对创建一个Java对象,其中value是该Java对象的实现类,而key是该Java对象放入对象池中的名字。为该程序提供如下属性配置文件。
a=java.util.Date
b=javax.swing.JFrame
编译、运行上面的ObjectPoolFactory程序,执行到main方法中的①号代码处,将看到输出系统当前时间——这表明对象池中已经有了一个名为a的对象,该对象是一个java.util.Date对象。执行到②号代码处,将看到输出一个JFrame对象。
提示:这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有用,大名鼎鼎的Spring框架就采用这种方式大大简化了JavaEE应用的开发。当然,Spring采用的是XML配置文件——毕竟属性文件能配置的信息太有限了,而XML配置文件能配置的信息就丰富多。
如果不想利用默认构造器来创建Java对象,而想利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器。为了利用指定的构造器来创建Java对象,需要如下三个步骤。
- 获取该类的Class对象。
- 利用Class对象的getConstructor()方法来获取指定的构造器。
- 调用Constructor的newInstance()方法来创建Java对象。
下面程序利用反射来创建一个JFrame对象,而且使用指定的构造器。
publicclassCreateJFrame{
publicstaticvoidmain(String[]args)throwsException{
//获取JFrame对应的Class对象
Class>jframeClazz=Class.forName("javax.swing.JFrame");
//获取JFrame中带一个字符串参数的构造器
Constructorctor=jframeClazz.getConstructor(String.class);
//调用Constructor的newInstance方法创建对象
Objectobj=ctor.newInstance("测试窗口");
//输出JFrame对象
System.out.println(obj);
}
}
上面程序中第一行粗休字代码用于获取JFrame类的指定构造器,前面已经提到:如果要唯一地确定某类中的构造器,只要指定构造器的形参列表即可。第一行粗体字代码获取构造器时传入了一个String类型,即表明想获取只有一个字符串参数的构造器。
程序中第二行粗体字代码使用指定构造器的newInstance()方法来创建一个Java对象,当调用Constructor对象的newInstance()方法时通常需要传入参数,因为调用Constructor的newInstance()方法实际上等于调用它对应的构造器,传给newInstance()方法的参数将作为对应构造器的参数。
对于上面的CreateFrame.java中已知java.swing.JFrame类的情形,通常没有必要使用反射来创建该对象,毕竟通过反射创建对象时性能要稍低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。
调用方法
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法——这两个方法的返回值是Method数组,或者Method对象。
每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用它对应的方法。在Method里包含一个Invoke()方法,该方法的签名如下。
- Objectinvoke(Objectobj,Object...args):该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。
下面程序对前面的对象池工厂进行加强,允许在配置文件中增加配置对象的成员变量的值,对象池工厂会读取为该对象配置的成员变量值,并利用该对象对应的setter方法设置成员变量的值。
publicclassExtendedObjectPoolFactory{
//定义一个对象池,前面是对象名,后面是实际对象
privateMapobjectPool=newHashMap<>();
privatePropertiesconfig=newProperties();
//从指定属性文件中初始化Properties对象
publicvoidinit(StringfileName){
try(FileInputStreamfis=newFileInputStream(fileName)){
config.load(fis);
}catch(IOExceptionex){
System.out.println("读取"+fileName+"异常");
}
}
//定义一个创建对象的方法
//该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
privateObjectcreateObject(StringclazzName)throwsException{
//根据字符串来获取对应的Class对象
Class>clazz=Class.forName(clazzName);
//使用clazz对应类的默认构造器创建实例
returnclazz.getConstructor().newInstance();
}
//该方法根据指定文件来初始化对象池
//它会根据配置文件来创建对象
publicvoidinitPool()throwsException{
for(Stringname:config.stringPropertyNames()){
//每取出一个key-value对,如果key中不包含百分号(%)
//这就表明是根据value来创建一个对象
//调用createObject创建对象,并将对象添加到对象池中
if(!name.contains("%")){
objectPool.put(name,createObject(config.getProperty(name)));
}
}
}
//该方法将会根据属性文件来调用指定对象的setter方法
publicvoidinitProperty()throwsInvocationTargetException,IllegalAccessException,NoSuchMethodException{
for(Stringname:config.stringPropertyNames()){
//每取出一对key-value对,如果key中包含百分号(%)
//即可认为该key用于控制调用对象的setter方法设置值
//%前半为对象名字,后半控制setter方法名
if(name.contains("%")){
//将配置文件中的key按%分割
String[]objAndProp=name.split("%");
//取出调用setter方法的参数值
Objecttarget=getObject(objAndProp[0]);
//获取setter方法名:set+"首字母大写"+剩下部分
StringmtdName="set"+objAndProp[1].substring(0,1).toUpperCase()+objAndProp[1].substring(1);
//通过target的getClass()获取它的实现类所对应的Class对象
Class>targetClass=target.getClass();
//获取希望调用的setter方法
Methodmtd=targetClass.getMethod(mtdName,String.class);
//通过Method的invoke方法执行setter方法
//将config.getProperty(name)的值作为调用setter方法的参数
mtd.invoke(target,config.getProperty(name));
}
}
}
publicObjectgetObject(Stringname){
//从objectPool中取出指定name对应的对象
returnobjectPool.get(name);
}
publicstaticvoidmain(String[]args)throwsException{
ExtendedObjectPoolFactoryepf=newExtendedObjectPoolFactory();
epf.init("extObj.txt");
epf.initPool();
epf.initProperty();
System.out.println(epf.getObject("a"));
}
}
上面程序中initProperty()方法里的第一行粗体字代码获取目标类中包含一个String参数的setter方法,第二行粗体字代码通过调用Method的invoke()方法来执行该setter方法,该方法执行完成后,就相当于执行了目标对象的setter方法。为上面程序提供如下配置文件。
a=javax.swing.JFrame
b=javax.swing.JLabel
#setthetitleofa
a%title=TestTitle
上面配置文件中的a%title行表明希望调用a对象的setTitle()方法,调用该方法的参数值为TestTitle。编译、运行上面的ExtendedObjectPoolFactory.java程序,可以看到输出一个JFrame窗口,该窗口的标题为TestTitle。
提示:Spring框架就是通过这种方式将成员变量值以及依赖对象等都放在配置文件中进行管理的,从而实现了较好的解耦。这也是Spring框架的IOC的秘密。
当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的如下方法。
- setAccessible(booleanflag):将Method对象的accessible设置为指定的布尔值。值为true,指示该Method在使用时应该取消Java语言的访问权限检查:值为false,则指示该Method在使用时要实施Java语言的访问权限检查。
注意:实际上,setAccessible()方法并不属于Method,而是属于它的父类AccessibleObject。因此Method、Constructor、Field都可调用该方法,从而实现通过反射来调用private方法、private构造器和成员变量,下一节将会让读者看到这种示例。也就是说,它们可以通过调用该方法来取消访问权限检查,通过反射即可访问private成员。
访问成员变量值
通过Class对象的getFields()或getField()方法可以获取该类所包括的全部成员变量或指定成员变量。Field提供了如下两组方法来读取或设置成员变量值。
- getXxx(Objectobj):获取obj对象的该成员变量的值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消get后面的Xxx。
- setXxx(Objectobj,Xxxval):将obj对象的该成员变量设置成值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消set后面的Xxx。
使用这两个方法可以随意地访问指定对象的所有成员变量,包括private修饰的成员变量。
classPerson{
privateStringname;
privateintage;
publicStringtoString(){
return"Person[name:"+name+",age:"+age+"]";
}
}
publicclassFieldTest{
publicstaticvoidmain(String[]args)throwsException{
//创建一个Person对象
Personp=newPerson();
//获取Person类对应的Class对象
ClasspersonClazz=Person.class;
//获取Person的名为name的成员变量
//使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
FieldnameField=personClazz.getDeclaredField("name");
//设置通过反射访问该成员变量时取消访问权限检查
nameField.setAccessible(true);
//调用set()方法为p对象的name成员变量设置值
nameField.set(p,"Yeeku.H.Lee");
//获取Person类名为age的成员变量
FieldageField=personClazz.getDeclaredField("age");
//设置通过反射访问该成员变量时取消访问权限检查
ageField.setAccessible(true);
//调用setInt()方法为p对象的age成员变量设置值
ageField.setInt(p,30);
System.out.println(p);
}
}
上面程序中先定义了一个Person类,该类里包含两个private成员变量:name和age,在通常情况下,这两个成员变量只能在Person类里访问。但本程序FieldTest的main()方法中6行粗体字代码通过反射修改了Person对象的name、age两个成员变量的值。
第一行粗体字代码使用getDeclaredField()方法获取了名为name的成员变量,注意此处不是使用getField()方法,因为getField()方法只能获取public访问控制的成员变量,而getDeclaredField()方法则可以获取所有的成员变量;第二行粗体字代码则通过反射访问该成员变量时不受访问权限的控制;第三行粗体字代码修改了Person对象的name成员变量的值。修改Person对象的age成员变量的值的方式与此完全相同。
编译、运行上面程序,会看到如下输出:
Person[name:Yeeku.H.Lee,age:30]
操作数组
在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array来动态地创建数组,操作数组元素等。
Array提供了如下几类方法。
- staticObjectnewInstance(Class>componentType,int...length):创建一个具有指定的元素类型、指定维度的新数组。
- staticxxxgetXxx(Objectarray,intindex):返回array数组中第index个元素。其中是各种基本数据类型,如果数组元素是引用类型,则该方法变为get(Objectarray,intindex)。
- staticvoidsetXxx(Objectarray,intindex,xxxval):将array数组中第index个元素的值设为val。其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变成set(Objectarray,intindex,Objectval)。
下面程序示范了如何使用Array来生成数组,为指定数组元素赋值,并获取指定数组元素的方式。
publicclassArrayTest1{
publicstaticvoidmain(Stringargs[]){
try{
//创建一个元素类型为String,长度为10的数组
Objectarr=Array.newInstance(String.class,10);
//依次为arr数组中index为5、6的元素赋值
Array.set(arr,5,"疯狂Java讲义");
Array.set(arr,6,"轻量级JavaEE企业应用实战");
//依次取出arr数组中index为5、6的元素的值
Objectbook1=Array.get(arr,5);
Objectbook2=Array.get(arr,6);
//输出arr数组中index为5、6的元素
System.out.println(book1);
System.out.println(book2);
}catch(Throwablee){
System.err.println(e);
}
}
}
上面程序中三行粗体字代码分别是通过Array创建数组,为数组元素设置值,访问数组元素的值的示例代码,程序通过使用Array就可以动态地创建并操作数组。
下面程序比上面程序稍微复杂一点,下面程序使用Array类创建了一个三维数组。
publicclassArrayTest2{
publicstaticvoidmain(Stringargs[]){
/*
*创建一个三维数组。根据前面介绍数组时讲的:三维数组也是一维数组,是数组元素是二维数组的一维数组,
*因此可以认为arr是长度为3的一维数组
*/
Objectarr=Array.newInstance(String.class,3,4,10);
//获取arr数组中index为2的元素,该元素应该是二维数组
ObjectarrObj=Array.get(arr,2);
//使用Array为二维数组的数组元素赋值。二维数组的数组元素是一维数组,
//所以传入Array的set()方法的第三个参数是一维数组。
Array.set(arrObj,2,newString[]{"疯狂Java讲义","轻量级JavaEE企业应用实战"});
//获取arrObj数组中index为3的元素,该元素应该是一维数组。
ObjectanArr=Array.get(arrObj,3);
Array.set(anArr,8,"疯狂Android讲义");
//将arr强制类型转换为三维数组
String[][][]cast=(String[][][])arr;
//获取cast三维数组中指定元素的值
System.out.println(cast[2][3][8]);
System.out.println(cast[2][2][0]);
System.out.println(cast[2][2][1]);
}
}
上面程序的第一行粗体字代码使用Array创建了一个三维数组,程序中较难理解的地方是第二段粗体字代码部分,使用Array为arrObj的指定元素赋值,相当于为二维数组的元素赋值。由于二维数组的元素是一维数组,所以程序传入的参数是一个一维数组对象。
运行上面程序,将看到cast[2][3][8]、cast[2][2][0]、cast[2][2][1]元素都有值,这些值就是刚才程序通过反射传入的数组元素值。
以上就是java使用反射创建并操作对象的方法的详细内容,更多关于JAVA反射创建并操作对象的资料请关注毛票票其它相关文章!