18.4 使用反射生成并操作对象 18.4.1创建对象
18.4 使用反射生成并操作对象
Class对象可以获得该类里的
- 方法(由
Method对象表示)、 - 构造器(由
Constructor对象表示)、 - 成员变量(由
Field对象表示),
这三个类都位于java.lang.reflect包下,并实现了java.lang.reflect.Member接口。
- 程序可以通过
Method对象来执行对应的方法, - 可以通过
Constructor对象来调用对应的构造器创建实例, - 可以通过
Field对象直接访问并修改对象的成员变量值
18.4.1 创建对象
通过反射生成对象的两种方式
通过反射来生成对象有如下两种方式。
- 使用
Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例. - 先使用
Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。
使用Class对象的newInstance方法
通过第一种方式来创建对象是比较常见的情形,因为在很多Java EE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据该字符串来创建对应的实例,就必须使用反射。
实例 简单对象池
下面程序就实现了一个简单的对象池,该对象池会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入一个HashMap中。
ObjectPoolFactory.java
1 | import java.util.*; |
上面程序中createObject()方法里的代码就是根据字符串来创建Java对象的关键代码,程序调用Class对象的newInstance()方法即可创建一个Java对象。程序中的initPool()方法会读取属性文件,对属性文件中每个key-value对创建一个Java对象,其中value是该Java对象的实现类,而key是该Java对象放入对象池中的名字。为该程序提供如下属性配置文件obj.txt.
obj.txt
1 | a=java.util.Date |
编译、运行上面的ObjectPoolFactory程序,
- 执行到
main方法中的①号代码处,将看到输出系统当前时间——这表明对象池中已经有了一个名为a的对象,该对象是一个java.util.Date对象。 - 执行到②号代码处,将看到输出一个
JFrame对象。
运行效果如下:
1 | Sun Jun 16 10:00:06 CST 2019 |
通过属性文件配置对象的应用
这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有用,大名鼎鼎的Spring框架就采用这种方式大大简化了Java EE应用的开发。当然,Spring采用的是XML配置文件—毕竟属性文件能配置的信息太有限了,而XML配置文件能配置的信息就丰富多了.
利用Constructor对象来创建对象
如果不想利用默认构造器来创建Java对象,而想利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器。为了利用指定的构造器来创建Java对象,需要如下三个步骤。
- 获取该类的
Class对象 - 利用
Class对象的getConstructor()方法来获取指定的构造器 - 调用
Constructor的newInstance()方法来创建Java对象。
实例
下面程序利用反射来创建一个JFrame对象,而且使用指定的构造器.
1 | import java.lang.reflect.*; |
运行结果:
1 | javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=测试窗口,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true] |
代码详解
上面程序中第二行代码(这里说的第二行是指main方法中第二个可执行代码,不包括注释):Constructor ctor = jframeClazz.getConstructor(String.class);用于获取JFrame类的指定构造器,前面已经提到:如果要唯一地确定某类中的构造器,只要指定构造器的形参列表即可。在获取构造器时传入了一个String类型,就表示获取只有一个字符串参数的构造器。
程序中第三行可执行代码使用指定构造器的newInstance()方法来创建一个Java对象,当调用Constructor对象的newInstance()方法时通常需要传入参数,因为调用Constructor的newInstance()方法实际上等于调用它对应的构造器,传给newInstance()方法的参数将作为对应构造器的参数.
小结
对于上面的CreateFrame.java中已知java.swing.JFrame类的情形,通常没有必要使用反射来创建该对象,因为通过反射创建对象时性能要稍低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。