下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

java中构建器内部的多形性方法的行为

作者:课课家教育     来源: http://www.kokojia.com点击数:617发布时间: 2016-02-21 16:14:33

标签: java动态绑定方法java开发java基础类

大神带你学编程,欢迎选课

  构建器调用的分级结构(顺序)为我们带来了一个有趣的问题,或者说让我们进入了一种进退两难的局面。若当前位于一个构建器的内部,同时调用准备构建的那个对象的一个动态绑定方法,那么会出现什么情况呢?在原始的方法内部,我们完全可以想象会发生什么——动态绑定的调用会在运行期间进行解析,因为对象不知道它到底从属于方法所在的那个类,还是从属于从它衍生出来的某些类。为保持一致性,大家也许会认为这应该在构建器内部发生。

java中构建器内部的多形性方法的行为_java动态绑定方法_java开发_课课家

  但实际情况并非完全如此。若调用构建器内部一个动态绑定的方法,会使用那个方法被覆盖的定义。然而,产生的效果可能并不如我们所愿,而且可能造成一些难于发现的程序错误。

  从概念上讲,构建器的职责是让对象实际进入存在状态。在任何构建器内部,整个对象可能只是得到部分组织——我们只知道基础类对象已得到初始化,但却不知道哪些类已经继承。然而,一个动态绑定的方法调用却会在分级结构里“向前”或者“向外”前进。它调用位于衍生类里的一个方法。如果在构建器内部做这件事情,那么对于调用的方法,它要操纵的成员可能尚未得到正确的初始化——这显然不是我们所希望的。

  通过观察下面这个例子,这个问题便会昭然若揭:

  //: PolyConstructors.java

  // Constructors and polymorphism

  // don't produce what you might expect.

  abstract class Glyph {

  abstract void draw();

  Glyph() {

  System.out.println("Glyph() before draw()");

  draw();

  System.out.println("Glyph() after draw()");

  }

  }

  class RoundGlyph extends Glyph {

  int radius = 1;

  RoundGlyph(int r) {

  radius = r;

  System.out.println(

  "RoundGlyph.RoundGlyph(), radius = "

  + radius);

  }

  void draw() {

  System.out.println(

  "RoundGlyph.draw(), radius = " + radius);

  }

  }

  public class PolyConstructors {

  public static void main(String[] args) {

  new RoundGlyph(5);

  }

  } ///:~

  在Glyph中,draw()方法是“抽象的”(abstract),所以它可以被其他方法覆盖。事实上,我们在RoundGlyph中不得不对其进行覆盖。但Glyph构建器会调用这个方法,而且调用会在RoundGlyph.draw()中止,这看起来似乎是有意的。但请看看输出结果:

  Glyph() before draw()

  RoundGlyph.draw(), radius = 0

  Glyph() after draw()

  RoundGlyph.RoundGlyph(), radius = 5

  当Glyph的构建器调用draw()时,radius的值甚至不是默认的初始值1,而是0。这可能是由于一个点号或者屏幕上根本什么都没有画而造成的。这样就不得不开始查找程序中的错误,试着找出程序不能工作的原因。

  前一节讲述的初始化顺序并不十分完整,而那是解决问题的关键所在。初始化的实际过程是这样的:

  (1) 在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零。

  (2) 就象前面叙述的那样,调用基础类构建器。此时,被覆盖的draw()方法会得到调用(的确是在RoundGlyph构建器调用之前),此时会发现radius的值为0,这是由于步骤(1)造成的。

  (3) 按照原先声明的顺序调用成员初始化代码。

  (4) 调用衍生类构建器的主体。

  采取这些操作要求有一个前提,那就是所有东西都至少要初始化成零(或者某些特殊数据类型与“零”等价的值),而不是仅仅留作垃圾。其中包括通过“合成”技术嵌入一个类内部的对象句柄。如果假若忘记初始化那个句柄,就会在运行期间出现违例事件。其他所有东西都会变成零,这在观看结果时通常是一个严重的警告信号。

  在另一方面,应对这个程序的结果提高警惕。从逻辑的角度说,我们似乎已进行了无懈可击的设计,所以它的错误行为令人非常不可思议。而且没有从编译器那里收到任何报错信息(C++在这种情况下会表现出更合理的行为)。象这样的错误会很轻易地被人忽略,而且要花很长的时间才能找出。

  因此,设计构建器时一个特别有效的规则是:用尽可能简单的方法使对象进入就绪状态;如果可能,避免调用任何方法。在构建器内唯一能够安全调用的是在基础类中具有final属性的那些方法(也适用于private方法,它们自动具有final属性)。这些方法不能被覆盖,所以不会出现上述潜在的问题。

赞(0)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程