第一章 基础
本章内容:
- 反射基础
- Class 基础
- 通过反射调用方法
我们常常遇到一些可以通过反射简单、优雅解决的问题。如果没有反射,解决方法变得凌乱、笨重、脆弱。考虑如下场景:
- 你的项目经理正在致力实现一个可插拔框架,该系统即使在构建、部署完成之后依然可以添加新组建。你创建了一些接口,准备了一个给 JAR 包打补丁的机制,但是你知道这并不能完全满足可插拔的需求。
- 在开发一个客户端应用几个月后,市场告诉你如果采用一个不同的远程机制将有助于销售,虽然更换方案是有利于商业的决定,但是你必须重新实现所有的远程接口。
- 你的模块的公用方法需要仅仅接受特定包内类的调用,以防止外部调用者滥用你的模块。你在所有 API 上添加一个参数,用来持有调用类的包名,但现在所有合法调用者必须修改他们的代码,而不受欢迎的代码可以通过伪造包名进行访问。
这些场景依次说明模块化、远程调用和安全的问题,他们似乎并无相似之处。但他们确实有相同的地方:他们都存在需求的变化,必须根据程序的结构修改代码才能满足需求。
重新实现接口、给 JAR 打补丁、修改方法调用都是无聊、机械的任务。他们如此机械,以至于可以通过算法来描述操作步骤:
- 检查程序的结构或者数据。
- 根据检查结果做出决定。
- 根据决定,修改程序的行为、结构或者数据。
虽然上述步骤在程序员看来非常熟悉,但是你无法想象程序能够做到。结果,修改代码的任务必须由一个坐在键盘前的程序员,而非运行在电脑上的程序来完成。学习反射可以让你超越前面的假设,让程序代替你修改代码。考虑下面简单的例子:
public class HelloWorld {
public void printName() {
System.out.println(this.getClass().getName());
}
}
下面这行代码
(new HelloWorld()).printName();
在标准输出打印 HelloWorld
字符串。现在假设 x
是 HelloWorld
类或者它的任意子类的实例,下行的代码
x.printName();
将把该类名字的字符串发送到标准输出。
这个简单的例子比它表面上更激动人心--它包含前面提到的所有步骤。首先,printName
方法检查调用对象的类型。检查结束之后,打印什么内容的决定代理给 x
对象的类(this.getClass()
)。最后该方法根据代理的返回结果打印不同的内容。如果没有被覆盖,printName
方法在 HelloWorld
的子类上表现与在 HelloWorld
类上不同。printName
方法非常灵活,它可以适应不同类型,从而表现出不同行为。在本书的例子中,我们会展现更多通过反射保持灵活性的方法。