@@ -1312,7 +1312,7 @@ x.getClass().equals(Derived.class)) true
13121312
13131313通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[ 附录:对象序列化] ( #ch040.xhtml#appendix-object-serialization ) )。但是,有时动态提取有关类的信息很有用。
13141314
1315- 考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示* 在该类定义中* 定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时。
1315+ 考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示* 在该类定义中* 定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时[ ^ 1 ] 。
13161316
13171317``` java
13181318// typeinfo/ShowMethods.java
@@ -1386,7 +1386,7 @@ public ShowMethods()
13861386*/
13871387```
13881388
1389- ` Class ` 方法 ` getmethods() ` 和' getconstructors()` 分别返回 ` Method` 数组和 ` Constructor` 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 ` toString()` ,生成带有整个方法签名的 ` String` 。代码的其余部分提取命令行信息,确定特定签名是否与目标 ` String` (使用 ` indexOf()`)匹配,并使用正则表达式(在 [ Strings] ( #ch021.xhtml#strings ) 一章中介绍)删除名称限定符。
1389+ ` Class ` 方法 ` getmethods() ` 和 ` getconstructors() ` 分别返回 ` Method ` 数组和 ` Constructor ` 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 ` toString() ` ,生成带有整个方法签名的 ` String ` 。代码的其余部分提取命令行信息,确定特定签名是否与目标 ` String ` (使用 ` indexOf() ` )匹配,并使用正则表达式(在 [ Strings] ( #ch021.xhtml#strings ) 一章中介绍)删除名称限定符。
13901390
13911391编译时无法知道 ` Class.forName() ` 生成的结果,因此所有方法签名信息都是在运行时提取的。如果你研究 JDK 反射文档,你将看到有足够的支持来实际设置和对编译时完全未知的对象进行方法调用(本书后面有这样的例子)。虽然最初你可能认为你永远都不需要这样做,但是反射的全部价值可能会令人惊讶。
13921392
@@ -1614,6 +1614,25 @@ boring3
16141614< ! -- Summary -- >
16151615## 本章小结
16161616
1617+ RTTI 允许通过匿名类的引用来获取类型信息。初学者极易误用它,因为在学会使用多态调用方法之前,这么做也很有效。有过程化编程背景的人很容易把程序组织成一系列 `switch ` 语句,你可以用 RTTI 和 `switch ` 实现功能,但这样就损失了多态机制在代码开发和维护过程中的重要价值。面向对象编程语言是想让我们尽可能的使用多态机制,只在非用不可的时候才使用 RTTI 。
1618+
1619+ 然而使用多态机制的方法调用,要求我们拥有基类定义的控制权。因为在你扩展程序的时候,可能会发现基类并未包含我们想要的方法。如果基类来自别人的库,这时 RTTI 便是一种解决之道:可继承一个新类,然后添加你需要的方法。在代码的其它地方,可以检查你自己特定的类型,并调用你自己的方法。这样做不会破坏多态性以及程序的扩展能力,因为这样添加一个新的类并不需要修改程序中的 `switch ` 语句。但如果想在程序中增加具有新特性的代码,你就必须使用 RTTI 来检查这个特定的类型。
1620+
1621+ 如果只是为了方便某个特定的类,就将某个特性放进基类里边,这将使得从那个基类派生出的所有其它子类都带有这些可能毫无意义的东西。这会导致接口更加不清晰,因为我们必须覆盖从基类继承而来的所有抽象方法,事情就变得很麻烦。举个例子,现在有一个表示乐器 `Instrument` 的类层次结构。假设我们想清理管弦乐队中某些乐器残留的口水,一种办法是在基类 `Instrument` 中放入 `clearSpitValve()` 方法。但这样做会导致类结构混乱,因为这意味着打击乐器 `Percussion`、弦乐器 `Stringed` 和电子乐器 `Electronic` 也需要清理口水。在这个例子中,RTTI 可以提供一种更合理的解决方案。可以将 `clearSpitValve()` 放在某个合适的类中,在这个例子中是管乐器 `Wind`。不过,在这里你可能会发现还有更好的解决方法,就是将 `prepareInstrument()` 放在基类中,但是初次面对这个问题的读者可能想不到还有这样的解决方案,而误认为必须使用 RTTI。
1622+
1623+ 最后一点,RTTI 有时候也能解决效率问题。假设你的代码运用了多态,但是为了实现多态,导致其中某个对象的效率非常低。这时候,你就可以挑出那个类,使用 RTTI 为它编写一段特别的代码以提高效率。然而必须注意的是,不要太早的关注程序的效率问题,这是个诱人的陷阱。最好先让程序能跑起来,然后再去看看程序能不能跑得更快,下一步才是去解决效率问题(比如使用 Profiler )[^ 5 ]。
1624+
1625+ 我们已经看到,反射,因其更加动态的编程风格,为我们开创了编程的新世界。但对有些人来说,反射的动态特性却是一种困扰。对那些已经习惯于静态类型检查的安全性的人来说,Java 中允许这种动态类型检查(只在运行时才能检查到,并以异常的形式上报检查结果)的操作似乎是一种错误的方向。有些人想的更远,他们认为引入运行时异常本身就是一种指示,指示我们应该避免这种代码。我发现这种意义的安全是一种错觉,因为总是有些事情是在运行时才发生并抛出异常的,即使是在那些不包含任何 `try` 语句块或异常声明的程序中也是如此。因此,我认为一致性错误报告模型的存在使我们能够通过使用反射编写动态代码。当然,尽力编写能够进行静态检查的代码是有价值的,只有你有这样的能力。但是我相信动态代码是将 Java 与其它诸如 C++ 这样的语言区分开的重要工具之一。
1626+
1627+ [^ 1 ]: 特别是在过去。但现在 Java 的 HTML 文档有了很大的提升,要查看基类的方法已经变得很容易了。
1628+
1629+ [^ 2 ]: 极限编程(XP ,Extreme Programming )的一条宗旨:“Try the simplest thing that could possibly work,实现尽最大可能的简单。”
1630+
1631+ [^ 3 ]: 最著名的例子是 Windows 操作系统,Windows 为开发者提供了公开的 API ,但是开发者还可以找到一些非公开但是可以调用的函数。为了解决问题,很多程序员使用了隐藏的 API 函数。这就迫使微软公司要像维护公开 API 一样维护这些隐藏的 API ,消耗了巨大的成本和精力。
1632+
1633+ [^ 4 ]: 比如,Python 中在元素前面添加双下划线 `__`,就表示你想隐藏这个元素。如果你在类或者包外面调用了这个元素,运行环境就会报错。
1634+
1635+ [^ 5 ]: 译者注:Java Profiler 是一种 Java 性能分析工具,用于在 JVM 级别监视 Java 字节码的构造和执行。主流的 Profiler 有 JProfiler 、YourKit 和 Java VisualVM 等。
16171636
16181637< ! -- 分页 -- >
16191638
0 commit comments