返回介绍

7.2.2 产生正确的行为

发布于 2024-10-15 23:56:17 字数 2819 浏览 0 评论 0 收藏 0

知道 Java 里绑定的所有方法都通过后期绑定具有多形性以后,就可以相应地编写自己的代码,令其与基础类沟通。此时,所有的衍生类都保证能用相同的代码正常地工作。或者换用另一种方法,我们可以“将一条消息发给一个对象,让对象自行判断要做什么事情。”

在面向对象的程序设计中,有一个经典的“形状”例子。由于它很容易用可视化的形式表现出来,所以经常都用它说明问题。但很不幸的是,它可能误导初学者认为 OOP 只是为图形化编程设计的,这种认识当然是错误的。

形状例子有一个基础类,名为 Shape;另外还有大量衍生类型:Circle(圆形),Square(方形),Triangle(三角形)等等。大家之所以喜欢这个例子,因为很容易理解“圆属于形状的一种类型”等概念。下面这幅继承图向我们展示了它们的关系:

上溯造型可用下面这个语句简单地表现出来:

Shape s = new Circle();

在这里,我们创建了 Circle 对象,并将结果句柄立即赋给一个 Shape。这表面看起来似乎属于错误操作(将一种类型分配给另一个),但实际是完全可行的——因为按照继承关系,Circle 属于 Shape 的一种。因此编译器认可上述语句,不会向我们提示一条出错消息。

当我们调用其中一个基础类方法时(已在衍生类里覆盖):

s.draw();

同样地,大家也许认为会调用 Shape 的 draw(),因为这毕竟是一个 Shape 句柄。那么编译器怎样才能知道该做其他任何事情呢?但此时实际调用的是 Circle.draw(),因为后期绑定已经介入(多形性)。

下面这个例子从一个稍微不同的角度说明了问题:

//: Shapes.java
// Polymorphism in Java

class Shape { 
  void draw() {}
  void erase() {} 
}

class Circle extends Shape {
  void draw() { 
    System.out.println("Circle.draw()"); 
  }
  void erase() { 
    System.out.println("Circle.erase()"); 
  }
}

class Square extends Shape {
  void draw() { 
    System.out.println("Square.draw()"); 
  }
  void erase() { 
    System.out.println("Square.erase()"); 
  }
}

class Triangle extends Shape {
  void draw() { 
    System.out.println("Triangle.draw()"); 
  }
  void erase() { 
    System.out.println("Triangle.erase()");
  }
}

public class Shapes {
  public static Shape randShape() {
    switch((int)(Math.random() * 3)) {
      default: // To quiet the compiler
      case 0: return new Circle();
      case 1: return new Square();
      case 2: return new Triangle();
    }
  }
  public static void main(String[] args) {
    Shape[] s = new Shape[9];
    // Fill up the array with shapes:
    for(int i = 0; i < s.length; i++)
      s[i] = randShape();
    // Make polymorphic method calls:
    for(int i = 0; i < s.length; i++)
      s[i].draw();
  }
} ///:~

针对从 Shape 衍生出来的所有东西,Shape 建立了一个通用接口——也就是说,所有(几何)形状都可以描绘和删除。衍生类覆盖了这些定义,为每种特殊类型的几何形状都提供了独一无二的行为。

在主类 Shapes 里,包含了一个 static 方法,名为 randShape()。它的作用是在每次调用它时为某个随机选择的 Shape 对象生成一个句柄。请注意上溯造型是在每个 return 语句里发生的。这个语句取得指向一个 Circle,Square 或者 Triangle 的句柄,并将其作为返回类型 Shape 发给方法。所以无论什么时候调用这个方法,就绝对没机会了解它的具体类型到底是什么,因为肯定会获得一个单纯的 Shape 句柄。

main() 包含了 Shape 句柄的一个数组,其中的数据通过对 randShape() 的调用填入。在这个时候,我们知道自己拥有 Shape,但不知除此之外任何具体的情况(编译器同样不知)。然而,当我们在这个数组里步进,并为每个元素调用 draw() 的时候,与各类型有关的正确行为会魔术般地发生,就象下面这个输出示例展示的那样:

Circle.draw()
Triangle.draw()
Circle.draw()
Circle.draw()
Circle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Square.draw()

当然,由于几何形状是每次随机选择的,所以每次运行都可能有不同的结果。之所以要突出形状的随机选择,是为了让大家深刻体会这一点:为了在编译的时候发出正确的调用,编译器毋需获得任何特殊的情报。对 draw() 的所有调用都是通过动态绑定进行的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文