多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
### [产生正确的行为](https://lingcoder.gitee.io/onjava8/#/book/09-Polymorphism?id=%e4%ba%a7%e7%94%9f%e6%ad%a3%e7%a1%ae%e7%9a%84%e8%a1%8c%e4%b8%ba) 一旦当你知道 Java 中所有方法都是通过后期绑定来实现多态时,就可以编写只与基类打交道的代码,而且代码对于派生类来说都能正常地工作。或者换种说法,你向对象发送一条消息,让对象自己做正确的事。 面向对象编程中的经典例子是形状**Shape**。这个例子很直观,但不幸的是,它可能让初学者困惑,认为面向对象编程只适合图形化程序设计,实际上不是这样。 形状的例子中,有一个基类称为**Shape**,多个不同的派生类型分别是:**Circle**,**Square**,**Triangle**等等。这个例子之所以好用,是因为我们可以直接说“圆(Circle)是一种形状(Shape)”,这很容易理解。继承图展示了它们之间的关系: ![形状继承图](https://lingcoder.gitee.io/onjava8/images/1562204648023.png) 向上转型就像下面这么简单: ~~~ Shape s = new Circle(); ~~~ 这会创建一个**Circle**对象,引用被赋值给**Shape**类型的变量 s,这看似错误(将一种类型赋值给另一种类型),然而是没问题的,因此从继承上可认为圆(Circle)就是一个形状(Shape)。因此编译器认可了赋值语句,没有报错。 假设你调用了一个基类方法(在各个派生类中都被重写): ~~~ s.draw() ~~~ 你可能再次认为**Shape**的`draw()`方法被调用,因为 s 是一个**Shape**引用——编译器怎么可能知道要做其他的事呢?然而,由于后期绑定(多态)被调用的是**Circle**的`draw()`方法,这是正确的。 下面的例子稍微有些不同。首先让我们创建一个可复用的**Shape**类库,基类**Shape**为它的所有子类建立了公共接口——所有的形状都可以被绘画和擦除: ~~~ // polymorphism/shape/Shape.java package polymorphism.shape; public class Shape { public void draw() {} public void erase() {} } ~~~ 派生类通过重写这些方法为每个具体的形状提供独一无二的方法行为: ~~~ // polymorphism/shape/Circle.java package polymorphism.shape; public class Circle extends Shape { @Override public void draw() { System.out.println("Circle.draw()"); } @Override public void erase() { System.out.println("Circle.erase()"); } } // polymorphism/shape/Square.java package polymorphism.shape; public class Square extends Shape { @Override public void draw() { System.out.println("Square.draw()"); } @Override public void erase() { System.out.println("Square.erase()"); } } // polymorphism/shape/Triangle.java package polymorphism.shape; public class Triangle extends Shape { @Override public void draw() { System.out.println("Triangle.draw()"); } @Override public void erase() { System.out.println("Triangle.erase()"); } } ~~~ **RandomShapes**是一种工厂,每当我们调用`get()`方法时,就会产生一个指向随机创建的**Shape**对象的引用。注意,向上转型发生在**return**语句中,每条**return**语句取得一个指向某个**Circle**,**Square**或**Triangle**的引用, 并将其以**Shape**类型从`get()`方法发送出去。因此无论何时调用`get()`方法,你都无法知道具体的类型是什么,因为你总是得到一个简单的**Shape**引用: ~~~ // polymorphism/shape/RandomShapes.java // A "factory" that randomly creates shapes package polymorphism.shape; import java.util.*; public class RandomShapes { private Random rand = new Random(47); public Shape get() { switch(rand.nextInt(3)) { default: case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } } public Shape[] array(int sz) { Shape[] shapes = new Shape[sz]; // Fill up the array with shapes: for (int i = 0; i < shapes.length; i++) { shapes[i] = get(); } return shapes; } } ~~~ `array()`方法分配并填充了**Shape**数组,这里使用了 for-in 表达式: ~~~ // polymorphism/Shapes.java // Polymorphism in Java import polymorphism.shape.*; public class Shapes { public static void main(String[] args) { RandomShapes gen = new RandomShapes(); // Make polymorphic method calls: for (Shape shape: gen.array(9)) { shape.draw(); } } } ~~~ 输出: ~~~ Triangle.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Circle.draw() ~~~ `main()`方法中包含了一个**Shape**引用组成的数组,其中每个元素通过调用**RandomShapes**类的`get()`方法生成。现在你只知道拥有一些形状,但除此之外一无所知(编译器也是如此)。然而当遍历这个数组为每个元素调用`draw()`方法时,从运行程序的结果中可以看到,与类型有关的特定行为奇迹般地发生了。 随机生成形状是为了让大家理解:在编译时,编译器不需要知道任何具体信息以进行正确的调用。所有对方法`draw()`的调用都是通过动态绑定进行的。