多态
多态指的是同一个接口可以在不同的条件下表现出不同的行为。多态通常包括三种形式:参数多态(重载、可变参数)、子类多态(重写)、泛型多态(泛型类、模板类)。
参数多态指的是一个函数可以接受不同类型的参数并做出对应的反应,在OOP语言中,这通常通过称之为“重载(overload)”的技术实现,该技术会为函数生成一个包含参数信息的签名(这个过程称之为mangle),然后在编译或解释时,根据参数信息在已存在的签名中进行匹配,如果匹配到合适的函数,就进行调用,否则就是一个编译错误。
子类多态指的是一个子类可以使用和父类同名的方法,但是执行与父类不同的行为。这是通过称之为重写(override)的技术实现的,在子类上调用函数时,编译器或解释器优先选择子类的函数执行,而非父类。一个更常见的用法是用一个父类或抽象类引用存储一个对象,然后调用他的某个方法。该方法会在不同的具体类型上进行不同的操作。
泛型多态指的是某种算法、操作、数据结构可以应用在不同的类型上,这通常是使用泛型类的技术实现的,泛型类接收一个类型参数,并对参数做某种限制(比如要求它必须是可比较的),使用这个类型参数声明一些方法或变量,以实现针对任意满足条件的类型进行相同的操作。
下面我们就来看看Java和C++的具体技术。
重载
java和C++的重载都是通过在编译时进行mangle并解析函数调用完成的。不同的是,C++中的函数声明具有类作用域,换言之,子类不能重载父类的方法。这被称之为“覆盖”。如果子类声明了一个和父类名称相同但参数不同的方法,将不能通过子类调用父类的该方法。解决方法是使用using关键字将父类方法的在子类作用域内声明,这样处于同一个作用域的方法就可以构成重载关系了。
而在Java中,子类可以重载父类型的方法,无需特殊操作,也不会覆盖父类型方法。
重写
java和C++都支持子类任意的重写父类型的方法,并且都要求返回协变的类型,都不支持逆协变参数的解析。不过这里有一个微妙的区别:动态绑定和静态绑定。在C++中,使用父类引用调用方法并不会自动的调用子类方法。只有使用子类引用才能访问子类的方法。解决办法是给函数增加virtual关键字,这将让编译器生成一张虚函数表。在调用时,在虚函数表中访问对应的函数指针进行调用,子类的虚函数比父类优先级更高,因此如果子类重写了方法,就会调用子类的方法。这被称之为“动态绑定”。
在Java中,会自动进行动态绑定。
泛型
java使用类型擦除的技术实现泛型,即生成一个泛型类,在类内将所有泛型参数转换为Object类型(或规定的父类)。而C++的模板使用展开替换技术实现泛型,即生成许多模板特化,针对不同的类型调用不同的模板。对于Java而言,一个原始的泛型(没有类型参数的泛型)和所有的泛型实例化都具有相同的类型。而对于C++而言,未特化的模板和全特化的模板是完全不同的类型。
为了在C++中能实现泛型多态,即java中List<?>这样的操作,可以让模板类继承一个普通类。由于所有模板类的特化都会继承这个普通类,就可以用这个普通类的引用访问任何模板类了。