类加载机制与反射4——使用反射生成并操作对象

本文深入讲解Java反射机制,包括如何通过反射创建对象、调用方法、访问成员变量及操作数组等核心功能。同时介绍了反射机制在Spring框架中的具体应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        Class对象可以获得该类里的方法(由Method对象表示)、构造器(由Constructor对象表示)、成员变量(由Field对象表示),这三个类都位于java.lang.reflect包下,并实现java.lang.reflect.Member接口。程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的成员变量值。

  • 创建对象
  • 调用方法
  • 访问成员变量值
  • 操作数组

1.创建对象

        通过反射来生成对象有如下两种方式:
    ①使用Class对象newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例。

    ②先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。

        通过第一种方式来创建对象是比较常见的情形,因为在很多JavaEE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据该字符串来创建对应的实例,就必须使用反射。

public class ObjectPoolFactory
{
	// 定义一个对象池,前面是对象名,后面是实际对象
	private Map<String ,Object> objectPool = new HashMap<>();
	// 定义一个创建对象的方法,
	// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
	private Object createObject(String clazzName)throws InstantiationException, IllegalAccessException , ClassNotFoundException
	{
		// 根据字符串来获取对应的Class对象
		Class<?> clazz = Class.forName(clazzName);
		// 使用clazz对应类的默认构造器创建实例
		return clazz.newInstance();
	}
	// 该方法根据指定文件来初始化对象池,
	// 它会根据配置文件来创建对象
	public void initPool(String fileName)throws InstantiationException, IllegalAccessException ,ClassNotFoundException
	{
		try(FileInputStream fis = new FileInputStream(fileName))
		{
			Properties props = new Properties();
			props.load(fis);
			for (String name : props.stringPropertyNames())
			{
				// 每取出一对key-value对,就根据value创建一个对象
				// 调用createObject()创建对象,并将对象添加到对象池中
				objectPool.put(name ,createObject(props.getProperty(name)));
			}
		}
		catch (IOException ex)
		{
			System.out.println("读取" + fileName + "异常");
		}

	}
	public Object getObject(String name)
	{
		// 从objectPool中取出指定name对应的对象。
		return objectPool.get(name);
	}

	public static void main(String[] args)
		throws Exception
	{
		ObjectPoolFactory pf = new ObjectPoolFactory();
		pf.initPool("obj.txt");
		System.out.println(pf.getObject("a"));      // ①
		System.out.println(pf.getObject("b"));      // ②
	}
}

        这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有用,大名鼎鼎的Spring框架就采用这种方式大大简化了JavaEE应用的开发。当然,Spring采用的是XML配置文件——毕竟属性文件能配置的信息太有限了,而XML配置文件能配置的信息就丰富多了。

public class CreateJFrame
{
	public static void main(String[] args)
		throws Exception
	{
		// 获取JFrame对应的Class对象
		Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
		// 获取JFrame中带一个字符串参数的构造器
		Constructor ctor = jframeClazz.getConstructor(String.class);
		// 调用Constructor的newInstance方法创建对象
		Object obj = ctor.newInstance("测试窗口");
		// 输出JFrame对象
		System.out.println(obj);
	}
}

        实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。

2.调用方法

        获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或者指定方法,每个Method对象对应一个方法,获得Method对象后,就可以通过该Method来调用它对应的方法。在Method里包含一个invoke()方法。当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的setAccessible(boolean flag)。

public class ExtendedObjectPoolFactory
{
	// 定义一个对象池,前面是对象名,后面是实际对象
	private Map<String ,Object> objectPool = new HashMap<>();
	private Properties config = new Properties();
	// 从指定属性文件中初始化Properties对象
	public void init(String fileName)
	{
		try(
			FileInputStream fis = new FileInputStream(fileName))
		{
			config.load(fis);
		}
		catch (IOException ex)
		{
			System.out.println("读取" + fileName + "异常");
		}
	}
	// 定义一个创建对象的方法,
	// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
	private Object createObject(String clazzName)throws InstantiationException, IllegalAccessException , ClassNotFoundException
	{
		// 根据字符串来获取对应的Class对象
		Class<?> clazz =Class.forName(clazzName);
		// 使用clazz对应类的默认构造器创建实例
		return clazz.newInstance();
	}
	// 该方法根据指定文件来初始化对象池,
	// 它会根据配置文件来创建对象
	public void initPool()throws InstantiationException,IllegalAccessException , ClassNotFoundException
	{
		for (String name : config.stringPropertyNames())
		{
			// 每取出一对key-value对,如果key中不包含百分号(%)
			// 这就标明是根据value来创建一个对象
			// 调用createObject创建对象,并将对象添加到对象池中
			if (!name.contains("%"))
			{
				objectPool.put(name ,
					createObject(config.getProperty(name)));
			}
		}
	}
	// 该方法将会根据属性文件来调用指定对象的setter方法
	public void initProperty()throws InvocationTargetException,IllegalAccessException,NoSuchMethodException
	{
		for (String name : config.stringPropertyNames())
		{
			// 每取出一对key-value对,如果key中包含百分号(%)
			// 即可认为该key用于控制调用对象的setter方法设置值,
			// %前半为对象名字,后半控制setter方法名
			if (name.contains("%"))
			{
				// 将配置文件中key按%分割
				String[] objAndProp = name.split("%");
				// 取出调用setter方法的参数值
				Object target = getObject(objAndProp[0]);
				// 获取setter方法名:set + "首字母大写" + 剩下部分
				String mtdName = "set" +
				objAndProp[1].substring(0 , 1).toUpperCase()
					+ objAndProp[1].substring(1);
				// 通过target的getClass()获取它实现类所对应的Class对象
				Class<?> targetClass = target.getClass();
				// 获取希望调用的setter方法
				Method mtd = targetClass.getMethod(mtdName , String.class);
				// 通过Method的invoke方法执行setter方法,
				// 将config.getProperty(name)的值作为调用setter的方法的参数
				mtd.invoke(target , config.getProperty(name));
			}
		}
	}
	public Object getObject(String name)
	{
		// 从objectPool中取出指定name对应的对象。
		return objectPool.get(name);
	}
	public static void main(String[] args)throws Exception
	{
		ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
		epf.init("extObj.txt");
		epf.initPool();
		epf.initProperty();
		System.out.println(epf.getObject("a"));
	}
}

        Spring框架就是通过这种方式将成员变量值以及依赖对象等都放在配置文件中进行管理的,从而实现了较好的解耦。这也是Spring框架的IOC的秘密。

3.访问成员变量值

        通过Class对象的getFields()或getField()方法可以获取该类所包含的全部成员变量或指定成员变量。Field提供了如下两组方法来读取或设置成员变量值。
getXxx(Object obj):获取obj对象的该成员变量的值。此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型,则取消get后面的Xxx.

setXxx(Object obj,Xxx val):将obj对象的该成员变量设置成val值。此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型,则取消set后面的Xxx.

class Person
{
	private String name;
	private int age;
	public String toString()
	{
		return "Person[name:" + name +
		" , age:" + age + " ]";
	}
}
public class FieldTest
{
	public static void main(String[] args)
		throws Exception
	{
		// 创建一个Person对象
		Person p = new Person();
		// 获取Person类对应的Class对象
		Class<Person> personClazz = Person.class;
		// 获取Person的名为name的成员变量
		// 使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
		Field nameField = personClazz.getDeclaredField("name");
		// 设置通过反射访问该成员变量时取消访问权限检查
		nameField.setAccessible(true);
		// 调用set()方法为p对象的name成员变量设置值
		nameField.set(p , "Yeeku.H.Lee");
		// 获取Person类名为age的成员变量
		Field ageField = personClazz.getDeclaredField("age");
		// 设置通过反射访问该成员变量时取消访问权限检查
		ageField.setAccessible(true);
		// 调用setInt()方法为p对象的age成员变量设置值
		ageField.setInt(p , 30);
		System.out.println(p);
	}
}

4.操作数组

        在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array来动态地创建数组,操作数组元素等。
Array提供了如下几类方法:
static Object newInstance(Class<?> componentType,int ... length):创建一个具有指定的元素类型、指定维度的新数组。
static xxx getXxx(Object array,int index):返回array数组中第index个元素。其中xxx是各种基本数据类型,如果数组元素时引用类型,则该方法变为get(Object array,int index).

static void setXxx(Object array,int index,xxx val):将array数组中第index个元素的值设为val.其中xxx是各种基本数据类型,如果数组元素时引用类型,则该方法变成set(Object array,int index,Object val).

public class ArrayTest1
{
	public static void main(String args[])
	{
		try
		{
			// 创建一个元素类型为String ,长度为10的数组
			Object arr = Array.newInstance(String.class, 10);
			// 依次为arr数组中index为5、6的元素赋值
			Array.set(arr, 5, "疯狂Java讲义");
			Array.set(arr, 6, "轻量级Java EE企业应用实战");
			// 依次取出arr数组中index为5、6的元素的值
			Object book1 = Array.get(arr , 5);
			Object book2 = Array.get(arr , 6);
			// 输出arr数组中index为5、6的元素
			System.out.println(book1);
			System.out.println(book2);
		}
		catch (Throwable e)
		{
			System.err.println(e);
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值