构建器调用的分级结构(顺序)为我们带来了一个有趣的问题或者说让我们进入了一种进退两难的局面若当前位于一个构建器的内部同时调用准备构建的那个对象的一个动态绑定方法那么会出现什么情况呢?在原始的方法内部我们完全可以想象会发生什么——动态绑定的调用会在运行期间进行解析因为对象不知道它到底从属于方法所在的那个类还是从属于从它衍生出来的某些类为保持一致性大家也许会认为这应该在构建器内部发生 但实际情况并非完全如此若调用构建器内部一个动态绑定的方法会使用那个方法被覆盖的定义然而产生的效果可能并不如我们所愿而且可能造成一些难于发现的程序错误 从概念上讲构建器的职责是让对象实际进入存在状态在任何构建器内部整个对象可能只是得到部分组织——我们只知道基础类对象已得到初始化但却不知道哪些类已经继承然而一个动态绑定的方法调用却会在分级结构里向前或者向外前进它调用位于衍生类里的一个方法如果在构建器内部做这件事情那么对于调用的方法它要操纵的成员可能尚未得到正确的初始化——这显然不是我们所希望的 通过观察下面这个例子这个问题便会昭然若揭 //: PolyConstructorsjava // Constructors and polymorphism // dont produce what you might expect abstract class Glyph { abstract void draw(); Glyph() { Systemoutprintln(Glyph() before draw()); draw(); Systemoutprintln(Glyph() after draw()); } } class RoundGlyph extends Glyph { int radius = ; RoundGlyph(int r) { radius = r; Systemoutprintln( RoundGlyphRoundGlyph() radius = + radius); } void draw() { Systemoutprintln( RoundGlyphdraw() radius = + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(); } } ///:~ 在Glyph中draw()方法是抽象的(abstract)所以它可以被其他方法覆盖事实上我们在RoundGlyph中不得不对其进行覆盖但Glyph构建器会调用这个方法而且调用会在RoundGlyphdraw()中止这看起来似乎是有意的但请看看输出结果 Glyph() before draw() RoundGlyphdraw() radius = Glyph() after draw() RoundGlyphRoundGlyph() radius = 当Glyph的构建器调用draw()时radius的值甚至不是默认的初始值而是这可能是由于一个点号或者屏幕上根本什么都没有画而造成的这样就不得不开始查找程序中的错误试着找出程序不能工作的原因 前一节讲述的初始化顺序并不十分完整而那是解决问题的关键所在初始化的实际过程是这样的 () 在采取其他任何操作之前为对象分配的存储空间初始化成二进制零 () 就象前面叙述的那样调用基础类构建器此时被覆盖的draw()方法会得到调用(的确是在RoundGlyph构建器调用之前)此时会发现radius的值为这是由于步骤()造成的 () 按照原先声明的顺序调用成员初始化代码 () 调用衍生类构建器的主体 采取这些操作要求有一个前提那就是所有东西都至少要初始化成零(或者某些特殊数据类型与零等价的值)而不是仅仅留作垃圾其中包括通过合成技术嵌入一个类内部的对象句柄如果假若忘记初始化那个句柄就会在运行期间出现违例事件其他所有东西都会变成零这在观看结果时通常是一个严重的警告信号 在另一方面应对这个程序的结果提高警惕从逻辑的角度说我们似乎已进行了无懈可击的设计所以它的错误行为令人非常不可思议而且没有从编译器那里收到任何报错信息(C++在这种情况下会表现出更合理的行为)象这样的错误会很轻易地被人忽略而且要花很长的时间才能找出 因此设计构建器时一个特别有效的规则是用尽可能简单的方法使对象进入就绪状态如果可能避免调用任何方法在构建器内唯一能够安全调用的是在基础类中具有final属性的那些方法(也适用于private方法它们自动具有final属性)这些方法不能被覆盖所以不会出现上述潜在的问题 |