《Java编程思想》第八章 多态

纵饮孤独 提交于 2019-12-15 16:00:02

在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。
多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。“封装”通过合并特征和行为来创建新的数据类型。多态也成为动态绑定后期绑定运行时绑定

再论向上转型

对象既可以作为它自己本身的类型使用,也可以作为它的基类型使用。而这种把某个对象的引用是为对其基类型的引用的做法被称为向上转型。

传记

方法调用绑定

将一个方调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定,叫做前期绑定
后期绑定就是在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行时绑定。Java除了static方法和final方法外,其他所有的方法都是后期绑定。
例如,有一个基类Shape以及多个导出类—Circle,Square,Triangle。
向上转型的经典语句就是:

Shape s = new Circle();

这里,创建一个Circle对象,并把得到的引用立即赋值给Shape,这样做看似是错误的,但实际是没有问题的,因为通过继承,Circle就是一种Shape

缺陷:“覆盖”私有方法

public class PrivateOverride{
    private void f(){print("private f()");}
    public static void main(String[] args){
         PrivateOverride po = new Derived();
         po.f();
    }
}

class Derived extends PrivateOverride{
    public void f(){print("public f()");}
}

最终运行的结果是:

private f()

我们所期望的是输出public f(),但是由于private方法被自动认为是final方法,而且对导出类是屏蔽的。因此,在这总情况下,Derived类中的f()方法就是一个全新的方法;既然基类中的f()方法在子类Derived中不可见,因此甚至也不能重载。

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

缺陷:域与静态方法

如果子类sub和父类super都拥有有一变量域名字叫做field。当涉及到多态时,Super sub = new Sub(),引用Sub中的field时所产生默认域并非Super版本的field域。因此,为了得到Super.field,必须显示地指明super.field。
如果某个方法是静态的,它的行为就不具有多态性。静态方法是与类,而并非单个的对象相关联的。

构造器和多态

构造器并不具有多态性(它们实际上是static方法,只不过该static声明是隐式的)。
含有三层继承的类在多态中调用构造器遵照的顺序:
(1)调用基类i构造器
(2)按声明顺序调用成员的初始化方法
(3)调用导出类构造器的主体

构造器内部的多态行为

例如:

class Glyph{
    void draw(){print("Glyph.draw()");}
    Glyph(){
          print("Glyph() before draw()");
          draw();
          print("Glyph() after draw()");
    }
}

class RoundGlyph extends Glyph{
     private int radius = 1;
     RoundGlyph(int r){
         radius = r;
         print("RoundGlyph.RoundGlyph() , radius = " + radius);
         
     }
     void draw(){
         print("RoundGlyph.draw() , radius = " + radius);
     }
}
public class PolyConstructors{
     public static void main(String[] args){
           new RoundGlyph(5);
}
}

最终结果为:

Glyph() before draw()
RoundGlyph.draw() , radius = 0
Glyph after draw()
RoundGlyph.RoundGlyph() , radius = 5

在此基础上,补充完整实际的调用构造器遵循的顺序为:
(1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零
(2)调用基类构造器,构造器中有其他的方法,则调用覆盖后的方法
(3)按照声明顺序调用成员的初始化方法
(4)调用导出类构造器主体

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!