Java反射机制的深入应用

前言

在上一篇文章中介绍了Java反射的基本概念以及基本应用,不熟悉的朋友可以点这里
本篇文章将重点介绍反射机制的深入应用

反射除了可以取得一个类的完整结构外,还可以调用类中的指定方法或指定属性,并且可以通过反射完成对数组的操作。

通过反射调用类中的方法

如果要使用反射调用类中的方法可以通过Method类完成,操作步骤如下:
(1) 通过Class类的getMethod(String name,Class….parameterTypes)方法取得一个Method的对象,并设置此方法操作时所需要的参数类型。
(2)之后可以使用invoke()进行调用,并向方法中传递要设置的参数。

下面通过一个具体的例子来为读者演示操作,此操作主要完成调用Person类中sayChina()方法的功能。(person类的定义在上一篇文章)

import java.lang.reflect.Method;
public class invokeSayChinaDemo{
public static void main(String[] args){
Class<?> c1=null;  //声明Class对象
try{
   c1=Class.forName("org.ljz.demo.Person"); //实例化Class对象 
}catch(ClassNotFoundException e){
e.printStackTrace();
}
try{
Method met=c1.getMethod("sayChina");  //此方法没有参数
met.invoke(c1.newInstance());  //调用方法,必须传递对象实例
}catch(Exception e){
e.printStackTtace();
     }
   }
}      

程序运行结果:

作者: ljz, 国籍: China

以上程序通过Class类的getMethod()方法根据一个类中的方法名称取得Method对象,并通过invoke()调用指定的方法。在使用invoke()方法时必须传入一个类的实例化对象,因为在sayChina()方法上没有任何的参数,所以此处没有设置参数类型和参数内容,本程序的操作如图所示
这里写图片描述

下面再为读者演示一个向方法中传递参数的实例,以调用Person类中的sayHello(String name;int age)方法为例,此方法需要传递两个参数。

package org.ljz.demo.invokedemo;
import java.lang.reflect.Method;
public class invokeSayHelloDemo{
public static void main(String[] args){
class<?> c1=null;      //声明Class对象
try{
c1=Class.forName("org.ljz.demo.Person"); //实例化Class对象
}catch(ClassNotFoundException e){
e.printStackTrace();
}
try{
Method met=c1.getMethod("sayHello", String.class,int.class);  //此方法需要两个参数
String rv=null;    //接收方法的返回值
//调用方法,必须传递对象实例,同时传递两个参数值
rv=(String)met.invoke(c1.newInstance(),"ljz",23);
System.out.println(rv);
}catch(Exception e){
e.printStackTrace();
     }
   }
}     

程序运行结果:

ljz,你好!我今年23岁了!

以上程序中,因为sayHello()方法本身要接收两个参数,所以在使用getMethod()方法调用时除了要指定调用的方法名称外,也需要接收参数的类型,由于sayHello()方法调用完之后存在返回值,而且返回值的类型是String,所以使用了一个字符串接收方法返回的内容。

调用setter及getter方法

熟悉面向对象的读者就知道”类中的属性必须封装,封装之后的属性要通过setter及getter”方法设置和取得,那么在使用反射进行调用方法操作中,最重要的是调用类中的setter及getter方法,这一点在Java的开发中随处可见,下面为读者演示如何完成这样的功能。直接调用Person类中的setter及getter方法

代码如下:

package org.ljz.demo.invokedemo;
import java.lang.reflect.Method;
public class InvokeSetGetDemo{
public static void main(String[] args){
Class<?> c1=null;   //声明Class对象
Object obj=null;   //声明一个对象
try{
c1=Class.forName("org.ljz.demo.Person");//实例化对象
}catch(ClassNotFountException e){
e.printStackTrace();
}
try{
obj=c1.newInstance();  //实例化操作对象
}catch(InstantiationException e){
e.printStackTrace();
}catch(IllegalAccessException e){
e.printStackTrace();
}
setter(obj,"name","ljz",String.class);   //调用setter方法
setter(obj,"age",23,int.class);  //调用setter方法
System.out.print("姓名:");  
getter(obj,"name");    //调用getter方法
System.out.print("年龄:");
getter(obj,"age");   //调用getter方法
}
/**
 *@param obj 操作的对象
 *@param att 操作的属性 
 *@param value 设置的值 
 *@param type 参数的类型
 */ 
 public static void setter(Object obj,String att,Object value, Class<?> type){  //调用setter方法
try{
    Method met=obj.getClass().getMethod("set"+initStr(att),type);//设置方法参数类型
met.invoke(obj,value);  //调用方法
}catch(Exception e){
e.printStackTrace();
  }
}

public static void getter(Object obj,String att){ //调用getter方法
try{
    Method met=obj.getClass().getMethod("get" + initStr(att));  //此方法不需要参数
System.out.println(met.invoke(obj));  //接收方法的返回值
}catch(Exception e){
e.printStackTrace();
  }
}
public static String initStr(String old){  //单词首字母大写
String str=old.substring(0,1).toUpperCase()+old.substring(1);
return str;
 }
}

程序运行结果:

姓名: ljz
年龄:23

以上程序完成了调用类中setter及getter方法的功能,下面我们分步介绍本程序的思路

(1) 在设置方法名称时,本程序直接传入的是类中的属性名称,例如name或age。但是实际上方法名称是setName(0,getName(),setAge(),getAge(),这样,所有属性名称的首字母需要大写,所以为了解决这样的问题,单独设置了一个方法initStr(),通过此方法将字符串中的首字母大写。首字母大写之后在增加set及get字符串以找到相应的方法。

(2)调用setter()方法时,传入了实例化对象,要操作的属性名称(在方法中会将其首字母大写),要设置的参数内容以及具体的参数类型,这样做是为了满足getMethod() 和invoke()方法的使用要求。

(3)在调用getter()方法时,也同样传入同一个实例化对象,因为其本身不需要任何的接收参数,所以只传入属性名称(在方法中会将其首字母大写), 并在此方法中将内容打印输出。

以上程序是在反射调用方法时的重要应用,读者一定掌握其原理。因为在以后的开发中,许多系统会为开发者实现好以上的功能。

通过反射操作属性

在反射操作中虽然可以使用Method调用类中的setter及getter方法设置和取得属性,但是这样操作毕竟很麻烦,所以在反射机制中也可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。但是在操作前首先需要注意的是,在类中的所有属性已经都设置成私有的访问权限,所以在使用set()或get()方法时首先要使用Field类中的setAccessible(true)方法将需要操作的属性设置成可以被外部访问。

代码如下:

package org.ljz.demo.invokedemo;
import java.lang.reflect.Field;
public class invokeFieldDemo{
public static void main(String[] args) throws Exception{
Class<?> c1=null;    //声明Class对象
Object obj=bull;     //声明一个对象
c1=Class.forName("org.ljz.demo.Person");//实例化Class对象
obj=c1.newInstance();  //实例化对象
Field nameField=null;  //表示name属性
Field ageField=null;  //表示age属性
naneField=c1.getDeclaredField("name");//取得name属性
ageField=c1.getDeclaredField("age");// 取得age属性
nameField.set(obj,"ljz");   //设置name属性内容
ageField.setAccessible(true); //将age属性设置成可以被外部访问
ageField.set(obj,23); //设置age属性内容
System.out.println("姓名:"+nameField.get(obj));
System.out.println("年龄:"+ageField.get(obj));
   }
}   

程序运行结果:

姓名:ljz
年龄:23

从以上代码不难看出,明显比之前使用setter或getter方法操作属性的代码更加简单,方便。

通过反射操作数组

反射机制不仅只能用在类上,还可以应用在任意的引用数据类型的数据上,当然,这也就包含了数组,即可以使用反射操作数组。可以通过Class类的以下方法取得一个数组的Class对象。

public Class<?> getComponentType()

在反射操作包java.lang.reflect中使用Array类表示一个数组,可以通过此类取得数组长度,取得数组内容的操作。Array类的常用方法如下所示:
这里写图片描述

下面通过两个范例让读者了解以上方法的使用

1. 取得数组信息并修改数组内容
package org.ljz.arraydemo;
import java.lang.reflect.Array;
public class ClassArrayDemo{
public static void main(String[] args)throws Exception{
int temp[]={1,2,3}; //声明一个整形数组
Class<?> c=temp.getClass().getComponentType();//取得数组的Class对象
System.out.println("类型" +c.getNmae()); //取得数组类型的名称
System.out.println("长度"+Array.getLength(temp));//得到数组的长度
System.out.println("第一个内容:"+Array.get(temp,0));//得到第一个内容
Array.set(temp,0,6);   //修改第一个内容
System.out.println("第一个内容"+Array.get(temp,0));//得到第一个内容
  }
 } 

程序运行结果:

类型: int
长度:3
第一个内容:1
第一个内容: 6

以上程序中通过Array类取得了数组的相关信息,并通过Array类中的set()方法修改了数组中的元素内容。

在应用中还可以通过Array类根据自己已有的数组类型来开辟新的数组对象,下面我们就来使用Array完成一个可以修改已有数组大小的功能。

2.修改数组的大小
package org.ljz.arraydemo
import java.lang.reflect.Array;

public class ChangeArrayDemo{
public static void main(String[] args)throws Exception{
int temp[]={1,2,3};
int newTemp[]=(int[])arrayInc(temp,5);//扩大数组的长度
String t[]={"zhangsan","lisi","wangwu"};//声明一个字符串数组
String nt[]=(String[])arrayInc(t,6);//扩大数组长度
print(nt);//打印数组信息
}

public static Object arrayinc(Object obj,int len){ //修改数组长度
Class<?> c=obj.getClass(); //通过数组得到Class对象
Class<?> arr=c.getComponentType();//得到数组的Class对象
Object newO=Array.newInstance(arr,len);//重新开辟新的数组空间
int co=Array.getLength(obj);//取得数组长度
System.arraycopy(obj,0,newO,0,co)//复制数组内容
return newO;
}

public static void print(Object obj){ //输出数组
Class<?> c=obj.getClass();  //通过数组得到Class对象
if(!c.isArray()){
 //判断是否是数组
return;
}
Class<?> arr=c.getComponentType();//取得数组的Class
System.out.println(arr.getName()+"数组的长度:"+Array.getLength(obj));//输出数组的信息
for(int i=0;i<Array.getLength(obj);i++){  //循环输出
System.out.print(Array.get(obj,i)+",");//通过Array输出
   }
  }
 }  

程序运行结果:

int数组的长度是:5
1,2,3,4,5
java.lang.String数组的长度是6
zhangsan ,lisi, wangwu, null, null, null

后续

下一篇我将介绍动态代理以及反射应用到工厂设计模式的相关应用,感兴趣的朋友可以关注一下!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值