多态是一项让程序员”将改变的事物与未变的事物分离开来“的技术。它可以消除类型之间的耦合关系,为程序员带来更快的程序开发过程,更好的代码组织,更好扩展的程序以及更容易的代码维护。
一、什么是多态
多态不但可以改善代码的组织结构和可读性,还能够创建可扩展的程序(即无论在项目最初创建时还是在需要添加新功能时都可以”生长“的程序)。之所以可以进行扩展,是因为在多态中默认忘记了对象类型,比如类Shap,无论是Circle(圆形)、Square(正方形)还是Triangle(三角形)都可以说是一种Shape,也就是继承中常提到的”is-a“关系,此时如果继续添加一个Rhombus(菱形)类仍然满足这种关系,所以当进行向上转型时都可以说是一个Shape类,即忽视了具体类型,多态实例如下:
class Shape{
public draw(){}
public clear(){}
}
class Trangle extends Shape{
//重写父类方法
public draw(){
System.out.print("Draw Trangle");
}
public clear(){
System.out.print("Clear Trangle");
}
}
class Circle extends Shape{
//重写父类方法
public draw(){
System.out.print("Draw Circle ");
}
public clear(){
System.out.print("Clear Circle ");
}
}
//可以进行扩展Square、Rhombus……
public class Test{
public static void main(String[] args){
Shape[] shape = {new Trangle(),new Circle()};//多态,忽视Trangle和Circle的具体类型而一并看作为Shape类型
for(Shape s:shape){
s.draw();
s.clear();
}
}
}
out:
Draw Trangle
Clear Trangle
Draw Circle
Clear Circle
二、实现机制——动态绑定
从上面的例子中我们可以看到当引用s调用draw()和clear()方法时并不是调用基类的方法,而是调用Trangle和Circle中各自重写的方法,这正是我们想要的,但是它是如何实现的呢?实际上编译器是无法得知的,这归功于方法调用时的绑定。
1.什么是绑定
将一个方法调用同一个方法主体关联起来被称为绑定——即将Trangle和其重写基类的方法进行关联。
2.分类
绑定分为前期绑定和后期绑定:
- 前期绑定是指程序执行前进行绑定,由编译器和连接程序实现。(这是一种面向过程语言中的默认绑定方式)
- 后期绑定是指在运行时根据对象的类型进行绑定,所以也被称为动态绑定或运行时绑定。
要想进行后期绑定就必须要有某种机制来触发,而这种机制就是方法调用机制,实际上编译器是一直不知道对象类型的,但是方法调用机制能找到正确的方法体,并加以调用。现实当中,Java语言中除了static和final(包括private)方法之外,其他方法都是后期绑定,也就是说在Java中后期绑定是自动发生的。
3.为什么Java中的static、final、private修饰的方法都不是后期绑定:
- static方法——静态方法本属于类,所以其从创建后就与类关联。
- final方法——此类方法是不能就行重写的,所以在调用过程中不会产生选择的歧义,所以不需要后期绑定。
- private方法——此类方法实际上是属于final方法的,所以理由与final一致。
三、多态的缺陷
多态对于私有方法、域和静态方法是不起作用的,原因如下:
- 私有方法:对于private方法实际是final类型的方法,所以其不能被覆盖,因此只能调用父类的私有方法。
- 域:排除父类与子类同名域易混淆这一点不谈,实际上俩个域是拥有俩块独立内存的,因此域不存在可以被覆盖的问题,且域的访问是由编译器解析的,既然是编译器解析,所以不会是动态绑定,因为动态绑定是在执行时进行的,所以不是多态。
- 静态方法:多态实际就是一种对象类型的向上转型,即将父类引用指向了子类对象,但是类本身不变,静态方法实际是与类直接关联的,而非单个的对象,所以静态方法不具有多态性。
**补充一点**:
既然有向上转型肯定就有向下转型,但是由于子类的方法不只包含所有的基类方法,还可能拥有基类没有的方法,所以在向下转型时很可能出现丢失方法的情况以致于转型失败,具体实例如下:
class Useful{
public void f(){}
public void g(){}
}
class MoreUserful extends Useful{
public void f(){}//重写父类方法
public void g(){}//重写父类方法
public void u(){}//子类包含基类没有的方法
}
public class Test{
public static void main(String[] args){
Useful[] u={new Useful(),new MoreUseful()}; //多态(包含向上转型)
u[0].f();
u[1].g();
//若要通过父类调用子类方法,需要向下转型
(MoreUseful)u[0].u();//报错ClassCastException异常
(MoreUseful)u[1].u();//向下转型成功!
}
}
在Java中所有转型都会进行检查,包括基本类型的强转。——运行时类型识别(RTTI)
多态中会遇到的异常——ClassCastException类转型异常 ,通常在向下转型时失败 。