面向对象的方法中,继承和多态是非常重要,也是非常好用的一个特性,理解和掌握这种特性是必须的。
多态的表现可以从两个大的方面来体现:1)由重载和覆盖造成的多态;2)在父类调用子类的过程中体现的多态。
第一类多态比较好理解,总的都可以看成是后面的方法和对象覆盖前面的方法和对象。这种覆盖包括类的数据成员和方法。比如,子类定义的数据和方法可以覆盖父类定义的数据和方法,同名的方法由于不同参数的调用造成的多态。如果子类要调用父类的成员和方法,必须经过super关键字来明确地引用。在super的用法上,Java与C++相比的一个不足是其引用只能对直接父辈进行,而对于祖父辈的成员和方法,则没有C++的由“类名::成员或方法名”的手段。
而从父辈动态地引用子辈的数据和方法,其实能能够体现多态的奇妙,也是一种更为好用的多态。因为现实中可能往往有这类的需求,比如我们定义了一个移动物的公共的基类,其中定义了一个公共的“移动”方法和公共的“速度”数据。而移动物可以派生出人、汽车、飞机等类,其速度和移动方法都在这些派生类中实现和赋值。则我们可能希望在某个函数中通过使用移动物类的移动方法和速度数据来在运行时动态的引用具体正确的方法和数据,或者我们希望在移动物类中直接使用其派生类的移动方法和速度数据。这种需求的实现能够极大地方便我们的编程。
在C++中,通过对父类的函数指定其为visual虚拟函数,即可以实现通过父类对象引用派生类对应的函数,而没有指定visual的函数则不具这种多态性。而Java中,所有的方法都具有这种虚函数的特性。如下所示:
class A {
public String d="A";
public void print() {
System.out.println(d);
}
public void com() {
print();
}
}
class B extends A{
public String d="B";
public void print() {
System.out.println(d);
}
}
class C extends B {
public String d="C";
}
定义了一个重载的方法print(),并且在类A中还定义了一个公共接口性函数com(),其中调用方法print()。则如下的程序由于父辈调用子辈的类似于C++虚函数特性的多态具有下面的结果:
public class test {
public static void use(A a) {
a.print();
}
public static void main(String[] args) {
A a=new A();
B b=new B();
C c=new C();
a.com();
b.com();
c.com();
use(a);
use(b);
use(c);
}
}
其运行结果为:A B B A B B
从结果看,不管是通过类A内部的定义中的方法com,还是通过在外部定义的以基类A为参数,并通过参数引用其方法print的函数use中,通过基类对象我们都正确地引用了子类的对应方法。这里我们还需要注意从B派生出来的类C,由于没有派生相应的方法print,所以实际调用的是继承层次中最后的一个print。
但是对于继承层次中的数据成员,则没有这种从父类访问子类的多态机制。这里,如果我们修改use函数如下:
public static void use(A a) {
System.out.println(a.d);
}
则运行结果为:A B B A A A
其原因是因为在函数中直接引用a的数据成员,这种试图通过基类对象引用子类对象的数据成员是不行的,因为在参数传递时,实例b和c已经被转换为类A,而虽然可以通过A的对象引用B和C重载的方法,但是是不能够直接引用B和C的数据成员的。如果想要实现这种效果,可能唯一的方法是直接传递相应的数据成员来完成,而试图通过传递类的实例来完成时不行的。如,可以在类A中添加如下的静态方法:
public static void prtD(String s) {
System.out.println(s);
}
然后进行如下的调用:
A.prtD(a.d);
A.prtD(b.d);
A.prtD(c.d);
则可以得到正确的结果:A B C