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
类的情形,通常没有必要使用反射来创建该对象,因为通过反射创建对象时性能要稍低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。