Class对象可以获得该类的方法(由Method对象表示)、构造器(有Constructor对象表示)、成员变量(由Field对象表示),这三个类都位于flect包下,并实现了flect.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对应类的默认构造器创建实例Constructor().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, Property(name)));}}catch (Exception ex){System.out.println("读取" + fileName + "异常");}}public Object getObject(String name){// 从objectPool中取出指定name对应的对象(name);}public static void main(String[] args) throws Exception{var pf = new ObjectPoolFactory();pf.initPool(");System.out.Object("obja")); // ①System.out.Object("objb")); // ②}
}
程序实现了一个简单的对象池,该对象池会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入一个HashMap中
//
obja=java.util.Date
objb=javax.swing.JFrame
这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式,随处可见比如Spring框架就是采用这个方式,只是配置文件形式不同,在Spring里采用XML作为配置文件
如果不想使用默认构造器来创建对象,而是利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器,使用指定构造器创建对象分3个步骤:
import flect.*;public class CreateJFrame
{public static void main(String[] args)throws Exception{// 获取JFrame对应的Class对象Class<?> jframeClazz = Class.forName("javax.swing.JFrame");// 获取JFrame中带一个字符串参数的构造器Constructor ctor = Constructor(String.class);// 调用Constructor的newInstance方法创建对象,这就相当于调用对应的构造器,传参也是同理相当于传给对应的构造器Object obj = wInstance("测试窗口");// 输出JFrame对象System.out.println(obj);}
}
实际上已知java.swing.JFrame类的具体情况下,没有必要使用反射来创建该对象,因为通过反射创建对象性能会低一些,只有当程序需要动态创建某个类的对象时才会考虑使用反射
在开发通用性比较广的框架、基础平台时可能会大量使用反射
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法,这两个方法的返回值是Method数组,或者Method对象
每个Method对象对应一个方法,获得Method对象后,程序就可以通过该Method来调用它对应的方法,在Method里包含一个invoke()方法,该方法的签名是Object invoke(Object obj, args)
该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参
import java.util.*;
import java.io.*;
import flect.*;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对应类的默认构造器创建实例Constructor().newInstance();}// 该方法根据指定文件来初始化对象池// 它会根据配置文件来创建对象public void initPool() throws Exception{for (var name : config.stringPropertyNames()){// 每取出一个key-value对,如果key中不包含百分号(%)// 这就表明是根据value来创建一个对象// 调用createObject创建对象,并将对象添加到对象池中if (!ains("%")){objectPool.put(name, Property(name)));}}}// 该方法将会根据属性文件来调用指定对象的setter方法public void initProperty() throws InvocationTargetException,IllegalAccessException, NoSuchMethodException{for (var name : config.stringPropertyNames()){// 每取出一对key-value对,如果key中包含百分号(%)// 即可认为该key用于控制调用对象的setter方法设置值// %前半为对象名字,后半控制setter方法名if (ains("%")){// 将配置文件中的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 = Class();// 获取希望调用的setter方法Method mtd = Method(mtdName, String.class);// 通过Method的invoke方法执行setter方法// 将Property(name)的值作为调用setter方法的参数mtd.invoke(target, Property(name));}}}public Object getObject(String name){// 从objectPool中取出指定name对应的对象(name);}public static void main(String[] args) throws Exception{var epf = new ExtendedObjectPoolFactory();epf.init(");epf.initPool();epf.initProperty();System.out.Object("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()方法可以获取该类所包括的全部成员变量或指定成员变量
使用这两个方法可以随意地访问指定对象的所有成员变量,包括private修饰的成员变量
import flect.*;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 = DeclaredField("name");// 设置通过反射访问该成员变量时取消访问权限检查nameField.setAccessible(true);// 调用set()方法为p对象的name成员变量设置值nameField.set(p, "Yeeku.H.Lee");// 获取Person类名为age的成员变量Field ageField = DeclaredField("age");// 设置通过反射访问该成员变量时取消访问权限检查ageField.setAccessible(true);// 调用setInt()方法为p对象的age成员变量设置值ageField.setInt(p, 30);System.out.println(p);}
}
定义了一个Person类,该类里包含两个private成员变量,name和age,通常情况下这两个成员变量只能在Person里访问,但在main()方法中通过反射修改了Person对象的name、age两个成员变量的值
flect里还提供了一个Array类,Array对象可以代表所有的数组,程序可以通过使用Array来动态地创建数组,操作数组元素等,Array类提供了如下方法:
get(Object array, int index)
import flect.*;public class ArrayTest1
{public static void main(String args[]){try{// 创建一个元素类型为String ,长度为10的数组Object arr = wInstance(String.class, 10);// 依次为arr数组中index为5、6的元素赋值Array.set(arr, 5, "我自横刀向天笑");Array.set(arr, 6, "去留肝胆两昆仑");// 依次取出arr数组中index为5、6的元素的值Object book1 = (arr, 5);Object book2 = (arr, 6);// 输出arr数组中index为5、6的元素System.out.println(book1);System.out.println(book2);}catch (Throwable e){println(e);}}
}
import flect.*;public class ArrayTest2
{public static void main(String args[]){/*创建一个三维数组。根据前面介绍数组时讲的:三维数组也是一维数组,是数组元素是二维数组的一维数组,因此可以认为arr是长度为3的一维数组*/Object arr = wInstance(String.class, 3, 4, 10);// 获取arr数组中index为2的元素,该元素应该是二维数组Object arrObj = (arr, 2);// 使用Array为二维数组的数组元素赋值。二维数组的数组元素是一维数组,// 所以传入Array的set()方法的第三个参数是一维数组。Array.set(arrObj, 2, new String[]{"我欲乘风归去","又恐琼楼玉宇"});// 获取arrObj数组中index为3的元素,该元素应该是一维数组。Object anArr = (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]);}
}
执行结果为
断肠人在天涯
我欲乘风归去
又恐琼楼玉宇
本文发布于:2024-02-04 23:26:51,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170718803860727.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |