Java-polymorphism-02

来源:互联网 发布:数据控制报警 编辑:程序博客网 时间:2024/05/14 09:42

提出问题:上例tune()方法接收一个Instrument引用。那么编译器怎样才能知道这个Instrument引用指向的是Wind对象,而不是Stringed对象或Brass对象呢?

回答问题:编译器无法得知,是“绑定”机制赋予的力量。

方法调用绑定

将一个方法调用和一个方法主体关联起来被称作“绑定”。若在程序执行前进行绑定(由编译器实现),叫做“前期绑定”(这是面向过程语言默认的绑定方式)。上诉程序之所以令人迷惑,主要是因为前期绑定。因为当编译器只有一个Instrument引用时,它无法知道究竟调用哪个方法才对。

解决的方法就是“后期绑定”:在运行时(而不是编译时)根据对象的类型进行绑定。Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。

产生正确的行为

**一旦知道Java中所有方法都是通过动态绑定实现多态这个事实之后,我们就可以编写只与基类打交道的程序代码了,并且这些代码对所有的导出类都可以正确运行。也就是说,发送消息给某个对象,让该对象去判定应该做什么事。

面向对象程序设计中,有一个经典的例子就是“几何形状”(shape):一个基类Shape和多个导出类CircleSquareTriangle等。

向上转型可以像下面这条语句这么简单:

Shape shape = new Circle();

这里创建了一个Circle对象,并把得到的引用shape赋值给Shape。这样看似错误(将一种类型赋值给另一种类型),但实际没有问题。假设你调用一个基类方法shape.draw();你可能认为调用的是Shapedraw(),因为这毕竟是一个Shape引用,但由于后期绑定(多态),编译器实际还是正确调用了Circle.draw()方法。

/**
 * Title: polymorphism in Java<br>
 * Description: 此例解释"多态"概念, 无任何功能接口<br>
 * Company: Augmentum Inc<br>
 * Copyright: (c) 2008 Thinking in Java<br>
 * 
@author Forest He
 * 
@version 1.0
 
*/

package com.augmentum.foresthe;

import java.util.Random;
public class Shapes {
    
private static RandomShapeGenerator gen = new RandomShapeGenerator();
    
public static void main(String[] args) {
        Shape[] shape 
= new Shape[10];
        
for(int i = 0; i < 10; i++) shape[i] = gen.next();
        
for(int i = 0; i < 10; i++) shape[i].draw();
    }

}


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

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

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

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

/* A factory that randomly creates shapes.*/
class RandomShapeGenerator {
    
private Random rand = new Random();
    
public Shape next() {
        
switch(rand.nextInt(3)) {
            
default:
            
case 0return new Circle();
            
case 1return new Square();
            
case 2return new Triangle();
        }

    }

}

/*
 * RandomShapeGenerator是一种工厂(factory), 每次调用next(), 它可以为随机选择的Shape对象产生一个引用. 请注意向上转型是在return语句里发生的. 每个return语句取得一个指向某个Circle、Square or Triangle的引用, 并将其以Shape类型从next()方法中发送出去.
 * 随机选择几何形状是为了让大家理解: 在编译时, 编译器不需要获得任何特殊信息就能进行正确的调用. 对draw()方法的所有调用都是通过动态绑定进行的.
 
*/

可扩展性

现在让我们回到“乐器”(Instrument)示例。由于多态机制,我们可根据自己的需求对系统添加任意多的新类型,而不需要改tune()方法。**在一个设计良好的OOP程序中,大多数甚至所有方法都会遵循tune()的模型,而且只与基类接口通信。这样的程序是“可扩展的”,因为可以从通用的基类继承出新的数据类型,从而新添一些功能。那些操纵基类接口的方法不需要任何改动就可以应用于新类。

提出问题:对于“乐器”例子,如果我们向基类中添加更多的方法,并加入一些新类,将会出现什么情况?

回答问题:不需要改动tune()方法,所有的新类都能和原有类一起正确运行。即使tune()方法是单独存放在某个文件中,并且在Instrument接口中添加了其他的新方法,tune()也不需要再编译就能正确运行。示例见书。

我们所做的代码修改,不会对程序中其他不应受到影响的部分产生破坏。换句话说,多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。

缺陷:“覆盖”私有方法

/**
 * Title: private method in override<br>
 * Description: 此例解释"多态"概念, 无任何功能接口<br>
 * Company: Augmentum Inc<br>
 * Copyright: (c) 2008 Thinking in Java<br>
 * 
@author Forest He
 * 
@version 1.0
 
*/

package com.augmentum.foresthe;
public class PrivateOverride {
    
private void f() { System.out.println("private f()"); }
    
public static void main(String[] args) {
        PrivateOverride po 
= new Derived();
        po.f();
    }

}

class Derived extends PrivateOverride {
    
public void f() { System.out.println("public f()"); }
}

我们所期望的输出是“public f()”,但是由于private方法被自动认为是final方法,而且对导出类是屏蔽的。因此,Derived类中的f()方法就是一个全新的方法;基类中的f()方法在子类Derived中不可见,也不能被继承和重载。

结论:只有非private方法才可以被覆盖;但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行。确切的说,在导出类中,对于基类中的private方法,最好采用不同的名字。

原创粉丝点击