Java面向对象系列[v1.0.0][使用反射生成并处理对象]

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

创建对象

通过反射来生成对象需要先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例,通过这种方式可以选择使用指定的构造器来创建实例
大部分JavaEE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据该字符串来创建对应的实例,就必须使用反射

import java.util.*;
import java.io.*;

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

程序实现了一个简单的对象池,该对象池会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入一个HashMap中

// obj.txt
obja=java.util.Date
objb=javax.swing.JFrame

这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式,随处可见比如Spring框架就是采用这个方式,只是配置文件形式不同,在Spring里采用XML作为配置文件

如果不想使用默认构造器来创建对象,而是利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器,使用指定构造器创建对象分3个步骤:

  • 获取该类的Class对象
  • 利用Class对象的getConstructor()方法来获取指定的构造器
  • 调用Constructor的newInstance()方法来创建Java对象
import java.lang.reflect.*;

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);
	}
}

实际上已知java.swing.JFrame类的具体情况下,没有必要使用反射来创建该对象,因为通过反射创建对象性能会低一些,只有当程序需要动态创建某个类的对象时才会考虑使用反射

在开发通用性比较广的框架、基础平台时可能会大量使用反射

调用方法

当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法,这两个方法的返回值是Method数组,或者Method对象
每个Method对象对应一个方法,获得Method对象后,程序就可以通过该Method来调用它对应的方法,在Method里包含一个invoke()方法,该方法的签名是Object invoke(Object obj, Object...args)该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参

import java.util.*;
import java.io.*;
import java.lang.reflect.*;

public class ExtendedObjectPoolFactory
{
	// 定义一个对象池,前面是对象名,后面是实际对象
	private Map<String, Object> objectPool = new HashMap<>();
	private Properties config = new Properties();
	// 从指定属性文件中初始化Properties对象
	public void init(String fileName)
	{
		try (
			var fis = new FileInputStream(fileName))
		{
			config.load(fis);
		}
		catch (IOException ex)
		{
			System.out.println("读取" + fileName + "异常");
		}
	}
	// 定义一个创建对象的方法
	// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
	private Object createObject(String clazzName)
		throws Exception
	{
		// 根据字符串来获取对应的Class对象
		Class<?> clazz = Class.forName(clazzName);
		// 使用clazz对应类的默认构造器创建实例
		return clazz.getConstructor().newInstance();
	}
	// 该方法根据指定文件来初始化对象池
	// 它会根据配置文件来创建对象
	public void initPool() throws Exception
	{
		for (var 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 (var 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
	{
		var epf = new ExtendedObjectPoolFactory();
		epf.init("extObj.txt");
		epf.initPool();
		epf.initProperty();
		System.out.println(epf.getObject("a"));
	}
}
a=javax.swing.JFrame
b=javax.swing.JLabel
#set the title of a
a%title=Test Title

a%title=Test Title表明希望调用a对象的setTitle()方法,调用该方法的参数值为Test Title,Spring框架就是使用这种方式将成员变量值以及依赖对象等都放在配置文件中进行管理的,从而实现了较好的解耦,这也是Spring框架IoC的秘密

当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限,如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的setAccessible(boolean flag)方法,将Method对象的accessible设置为指定的布尔值,值为true的时候,指示该Method在使用时应该取消Java语言的访问权限检查,值为false的时候,则指示该Method在使用时要实施Java语言的访问权限检查

实际上,setAccessible()方法并不属于Method,而是属于它的父类AccessibleObject,因此Method、Constructor、Field都可以调用该方法,从而实现通过反射来调用private方法、private构造器和private成员变量,也就是说通过调用该方法可以取消访问权限检查,通过反射即可访问private成员

访问成员变量值

通过Class对象的getFields()或getField()方法可以获取该类所包括的全部成员变量或指定成员变量

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

使用这两个方法可以随意地访问指定对象的所有成员变量,包括private修饰的成员变量

import java.lang.reflect.*;

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对象
		var 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);
	}
}

定义了一个Person类,该类里包含两个private成员变量,name和age,通常情况下这两个成员变量只能在Person里访问,但在main()方法中通过反射修改了Person对象的name、age两个成员变量的值

操作数组

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)
import java.lang.reflect.*;

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, "我自横刀向天笑");
			Array.set(arr, 6, "去留肝胆两昆仑");
			// 依次取出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);
		}
	}
}

import java.lang.reflect.*;

public class ArrayTest2
{
	public static void main(String args[])
	{
		/*
		  创建一个三维数组。
		  根据前面介绍数组时讲的:三维数组也是一维数组,
		  是数组元素是二维数组的一维数组,
		  因此可以认为arr是长度为3的一维数组
		*/
		Object arr = Array.newInstance(String.class, 3, 4, 10);
		// 获取arr数组中index为2的元素,该元素应该是二维数组
		Object arrObj = Array.get(arr, 2);
		// 使用Array为二维数组的数组元素赋值。二维数组的数组元素是一维数组,
		// 所以传入Array的set()方法的第三个参数是一维数组。
		Array.set(arrObj, 2, new String[]
		{
			"我欲乘风归去",
			"又恐琼楼玉宇"
		});
		// 获取arrObj数组中index为3的元素,该元素应该是一维数组。
		Object anArr = Array.get(arrObj, 3);
		Array.set(anArr, 8, "断肠人在天涯");
		// 将arr强制类型转换为三维数组
		var 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]);
	}
}

执行结果为

断肠人在天涯
我欲乘风归去
又恐琼楼玉宇

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/105973761