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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.util.*;
import java.io.*;
public class ObjectPoolFactory
{
// 定义一个对象池,key是对象名,value是实际对象
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")); // ②
}
}

上面程序中createObject()方法里的代码就是根据字符串来创建Java对象的关键代码,程序调用Class对象的newInstance()方法即可创建一个Java对象。程序中的initPool()方法会读取属性文件,对属性文件中每个key-value对创建一个Java对象,其中value是该Java对象的实现类,而key是该Java对象放入对象池中的名字。为该程序提供如下属性配置文件obj.txt.

obj.txt

1
2
a=java.util.Date
b=javax.swing.JFrame

编译、运行上面的ObjectPoolFactory程序,

  • 执行到main方法中的①号代码处,将看到输出系统当前时间——这表明对象池中已经有了一个名为a的对象,该对象是一个java.util.Date对象。
  • 执行到②号代码处,将看到输出一个JFrame对象。

运行效果如下:

1
2
Sun Jun 16 10:00:06 CST 2019
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]

通过属性文件配置对象的应用

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

利用Constructor对象来创建对象

如果不想利用默认构造器来创建Java对象,而想利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器。为了利用指定的构造器来创建Java对象,需要如下三个步骤。

  1. 获取该类的Class对象
  2. 利用Class对象的getConstructor()方法来获取指定的构造器
  3. 调用ConstructornewInstance()方法来创建Java对象。

实例

下面程序利用反射来创建一个JFrame对象,而且使用指定的构造器.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.lang.reflect.*;
public class CreateJFrame
{
public static void main(String[] args)
throws Exception
{
// 1.获取JFrame对应的Class对象
Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
// 2.获取JFrame中带一个字符串参数的构造器
Constructor ctor = jframeClazz.getConstructor(String.class);
// 3.调用Constructor的newInstance方法创建对象
Object obj = ctor.newInstance("测试窗口");
// 输出JFrame对象
System.out.println(obj);
}
}

运行结果:

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()方法时通常需要传入参数,因为调用ConstructornewInstance()方法实际上等于调用它对应的构造器,传给newInstance()方法的参数将作为对应构造器的参数.

小结

对于上面的CreateFrame.java中已知java.swing.JFrame类的情形,通常没有必要使用反射来创建该对象,因为通过反射创建对象时性能要稍低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。