JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
核心反射机制 java.lang.reflect,提供了“通过程序来访问关于已装载的类的信息”的能力。给定一个Class实例,你可以获得Constructor、Method和Field实例,分別代表了该Class实例所表示的类的Constructor( 构造器)、Method( 方法)和Held( 域)。这些对象提供了“通过程序来访问类的成员名称、域类型、方法签名等信息”的能力。
而且,Constructor、Method和Field实例使你能够通过反射机制操作它们的底层对等体:通过调用Constructor、Method和Field实例上的方法,可以构造底层类的实例、调用底层类的方法,并访问底层类中的域。例如,Method.invoke使你可以调用任何类的任何对象上的任何方法(遵从常规的安全限制)反射机制(reflection )允许一个类使用另一个类,即使当前者被编译的时候后者还根本不存在。然而,这种能力也要付出代价:
• 丧失了编译时类型检查的好处,包括异常检査。如果程序企图用反射方式调用不存在的或者不可访问的方法,在运行时它将会失败,除非采取了特别的预防措施。
• 执行反射访问所需要的代码非常笨拙和冗长。编写这样的代码非常乏味,阅读起来也很困难。
• 性能损失。反射方法调用比普通方法调用慢了许多。具体慢了多少,这很难说,因为受到了多个因素的影响。在我的机器上,速度的差异可能小到2倍,也可能大到50倍。
核心反射机制最初是为了基于组件的应用创建工具而设计的。这类工具通常要根据需要装载类,并且用反射功能找出它们支持哪些方法和构造器。这些工具允许用户交互式地构建出访问这些类的应用程序,但是所产生出来的这些应用程序能够以正常的方式访问这些类,而不是以反射的方式。反射功能只是在设计时被用到。通常,普通应用程序在运行时不应该以反射方式访问对象。
有一些复杂的应用程序需要使用反射机制。这些示例中包括类浏览器、对象监视器、代码分析工具、解释型的内嵌式系统。在RPC( 远程过程调用)系统中使用反射机制也是非常合适的,这样可以不再需要 。如果你对自己的应用程序是否也属于这一类应用程序而感到怀疑,它很有可能就不属于这一类。
如果只是以非常有限的形式使用反射机制,虽然也要付出少许代价,但是可以获得许多好处。对于有些程序,它们必须用到在编译时无法获取的类,但是在编译时存在适当的接口或者超类,通过它们可以引用这个类(见第52条:通过接口引用对象)。如果是这种情况,就可以以反射方式创建实例, 然后通过它们的接口或者超类,以正常的方式访问这些实例。如果适当的构造器不带参数,甚至根本不需要使用java.lang.reflect包,Class.newlnstance方法就已经提供了所需的功能。
例如,下面的程序创建了一个Set<String>实例,它的类是由第一个命令行参数指定的。 该程序把其余的命令行参数插入到这个集合中,然后打印该集合。不管第一个参数是什么,程序都会打印出余下的命令行参数,其中重复的参数会被消除掉。这些参数的打印顺序取决于第一个参数中指定的类。如果指定“java.util.HashSet”,显然这些参数就会以随机的顺序打印出来,如果指定“java.util.TreeSet”,则它们就会按照字母顺序打印出来,因为TreeSet中的元素是排好序的。相应的代码如下:
尽管这个程序就像一个“玩偶”,但是它所演示的这种方法是非常强大的。这个玩偶程序可以很容易地变成一个通用的集合测试器,通过侵入式地操作一个或者多个集合实例,并检査是否遵守Set接口的约定,以此来验证指定的Set实现。同样地,它也可以变成一个通用的集合性能分析工具。实际上,它所演示的这种方法足以实现一个成熟的服务提供者框架 ( 见第1条:考虑用静态工厂方法代替构造器)。绝大多数情况下,使用反射机制时需要的也正是这种方法。
这个示例演示了反射机制的两个缺点。第一,这个例子会产生3个运行时错误,如果不使用反射方式的实例化,这3个错误都会成为编译时错误。第二,根据类名生成它的实例需要20行冗长的代码,而调用一个构造器可以非常简洁地只使用一行代码。然而,这些缺点还仅仅局限于实例化对象的那部分代码。一旦对象被实例化,它与其他的Set实例就难以区分。在实际的程序中,通过这种限定使用反射的方法,绝大部分代码可以不受影响。
如果试着编译这个程序,会得到下面的错误消息:
这条警告与程序中使用了泛型有关,但它并不能说明真正的问题。要了解禁止这种警告的最佳方法,(请参见第24条:消除非受检警告)。
另一个值得注意的附带问题是,这个程序使用了System.exit。很少有需要调用这个方法的时候,它会终止整个VM(虚拟机)。但是,它对于命令行有效性的非法终止是很合适的。
类对于在运行时可能不存在的其他类、方法或者域的依赖性,用反射法进行管理,这种用法是合理的,但是很少使用。如果要编写一个包,并且它运行的时候必须依赖其他某个包的多个版本,这种做法可能就非常有用。这种做法就是,在支持包所需要的最小环境下对它进行编译,通常是最老的版本,然后以反射方式访问任何更加新的类或者方法。如果企图访问的新类或者新方法在运行时不存在,为了使这种方法有效你还必须采取适当的动作。所谓适当的动作,可能包括使用某种其他可替换的办法来达到同样的目的,或者使用简化的功能进行处理。
简而言之,反射机制是一种功能强大的机制,对于特定的复杂系统编程任务,它是非常必要的,但它也有一些缺点。如果你编写的程序必须要与编译时未知的类一起工作,如有可能,就应该仅仅使用反射机制来实例化对象,而访问对象时则使用编译时已知的某个接口或者超类。