将一个方法调用同一个方法主体关联起来被称为绑定。
若在程序执行前期进行绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定。
当编译器只有一个引用时,它无法知道究竟调用哪个方法才对,解决的办法就是后期绑定,它的含义就是在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行时绑定。
java中除了static方法和final(private方法属于final方法)之外,其他所有的方法都是后期绑定。这意味着同城情况下,我们不必判定是否应该进行后期绑定----它会自动发生。
有一个基类Shape,以及多个导出类--Circle、Square、Triangle等,每个类中都有draw(),erase()方法,向上转型可以像下条语句这么简单:
Shape s = new Circle();
这里,创建了一个Circle对象,并把得到的引用立即赋值给Shape,这样看似错误(将一种类型赋值给另一种类型);但实际上是没有问题的,因为通过继承,Circle就是一种Shape。
假设你调用一个基类方法(它已在导出类中被覆盖)
s.draw();
你可能认为调用的是Shape的draw(),因为这毕竟是一个Shape的引用,那么编译器是怎么知道去做其他的事情呢?由于后期绑定(多态),还是调用了Circle.draw()方法。
下面的例子稍微有不同:
public class Shape { public void draw(){} public void erase(){} } public class Circle extends Shape { public void draw() { System.out.println("Circle.draw()"); } public void erase() { System.out.println("Circle.erase()"); } } public class Square extends Shape { public void draw() { System.out.println("Square.draw()"); } public void erase() { System.out.println("Square.erase()"); } } public class Triangle extends Shape { public void draw() { System.out.println("Triangle.draw()"); } public void erase() { System.out.println("Triangle.erase()"); } } public class RandomShapeGenerator { private Random rand = new Random(47); public Shape next() { switch(rand.nextInt(3)) { default: case 0 : return new Circle(); case 1 : return new Square(); case 2 : return new Triangle(); } } } public class Shapes { private static RandomShapeGenerator rsg = new RandomShapeGenerator(); public static void main(String[] args) { Shape [] s = new Shape[9]; for (int i = 0; i < s.length; i++) s [i] = rsg.next(); for (Shape shp : s) { shp.draw(); } } } 输出为: Triangle.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Circle.draw()
Shape基类为自它那里继承来的所有导出类建立了一个公用接口----也就是说,所有形状都是可描绘和擦除。导出类通过覆盖这些定义,来为每种特殊类型的几何形状提供单独的行为。
RandomShapeGenerator是一种“工厂”,在我们每次调用next()方法时,它可以随机选择的为Shape对象产生一个引用。请注意向上转型是在return语句后发生的。每个return语句取得一个指向某个Circle、Square或者Triangle的引用,并将其以Shape类型从next()方法中发送出去。所以无论我们在什么时候调用next()方法时,是绝对不可能知道具体类型到底是什么的,因为我们总是只能获得一个通用的Shape引用。
main()包含了一个Shape引用组成的数组,通过调用RandomShapeGenerator.next()来填入数据。此时,我们只知道自己拥有一些Shape,初次之外不会知道更具体的情况(编译器也不知道)。然而,当我们遍历数组时,并为每个数组元素调用draw()方法时,与类型有关的特定行为会神奇地正确发生。
随机选择几何形状是为了更好的看出编译器不需要获得任何特殊信息就能进行正确的调用。对draw()方法的所有调用都是通过动态绑定进行的。