在java中发生正确的行为
知道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()的所有挪用都是通过动态绑定举办的。