zoukankan      html  css  js  c++  java
  • 多态性理解

    多形对象的互换使用
    通常, 继承最终会以创建一系列类收场, 所有类都建立在统一的接口基础上。
    我们用一幅颠倒的树形图来阐明这一点(注释⑤) :
    ⑤:这儿采用了“统一记号法” ,本书将主要采用这种方法。
    35 页图
    对这样的一系列类,我们要进行的一项重要处理就是将衍生类的对象当作
    础类的一个对象对待。这一点是非常重要的,因为它意味着我们只需编写单一的
    代码,令其忽略类型的特定细节,只与基础类打交道。这样一来,那些代码就可
    与类型信息分开。所以更易编写,也更易理解。此外,若通过继承增添了一种新
    类型,如“三角形” ,那么我们为“几何形状”新类型编写的代码会象在旧类型
    里一样良好地工作。所以说程序具备了“扩展能力” ,具有“扩展性” 。
    以上面的例子为基础,假设我们用 Java 写了这样一个函数:
    35-36 页程序
    void doStuff(Shape s) {
    s.erase();
    // ...
    s.draw();
    }
    Circle c = new Circle();
    Triangle t = new Triangle();
    Line l = new Line();
    doStuff(c);
    doStuff(t);
    Shape
    Draw()
    Clear()
    SetColor()
    GetColor()
    Move()
    Circle Square Trangle
    8
    doStuff(l);
    这个函数可与任何“几何形状” (Shape)通信,所以完全独立于它要描绘
    (draw)和删除(erase)的任何特定类型的对象。如果我们在其他一些程序里使
    用 doStuff()函数:
    36 页上程序
    void doStuff(Shape s) {
    s.erase();
    // ...
    s.draw();
    }
    那么对 doStuff()的调用会自动良好地工作,无论对象的具体类型是什么。
    这实际是一个非常有用的编程技巧。请考虑下面这行代码:
    doStuff(c);
    此时,一个 Circle(圆)句柄传递给一个本来期待 Shape(形状)句柄的函
    数。由于圆是一种几何形状,所以 doStuff()能正确地进行处理。也就是说,凡是
    doStuff()能发给一个 Shape 的消息,Circle 也能接收。所以这样做是安全的,不
    会造成错误。
    我们将这种把衍生类型当作它的基本类型处理的过程叫作“Upcasting”(上
    溯造型) 。其中, “cast”(造型)是指根据一个现成的模型创建;而“Up”(向上)
    表明继承的方向是从 “上面” 来的——即基础类位于顶部, 而衍生类在下方展开。
    所以,根据基础类进行造型就是一个从上面继承的过程,即“Upcasting”。
    在面向对象的程序里,通常都要用到上溯造型技术。这是避免去调查准确类
    型的一个好办法。请看看 doStuff()里的代码:
    s.erase();
    // ...
    s.draw();
    注意它并未这样表达: “如果你是一个 Circle,就这样做;如果你是一个
    Square,就那样做;等等” 。若那样编写代码,就需检查一个 Shape 所有可能的
    类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的 Shape
    类型后,都要相应地进行修改。在这儿,我们只需说: “你是一种几何形状,我
    知道你能将自己删掉,即 erase();请自己采取那个行动,并自己去控制所有的细
    节吧。 ”
    1.6.1 动态绑定
    在 doStuff()的代码里,最让人吃惊的是尽管我们没作出任何特殊指示,采取
    的操作也是完全正确和恰当的。我们知道,为 Circle 调用 draw()时执行的代码与
    为一个 Square 或 Line 调用 draw()时执行的代码是不同的。但在将 draw()消息发
    给一个匿名 Shape 时,根据 Shape 句柄当时连接的实际类型,会相应地采取正确
    的操作。这当然令人惊讶,因为当 Java 编译器为 doStuff()编译代码时,它并不
    知道自己要操作的准确类型是什么。 尽管我们确实可以保证最终会为 Shape 调用
    erase(),为 Shape 调用 draw(),但并不能保证为特定的 Circle,Square 或者 Line
    调用什么。然而最后采取的操作同样是正确的,这是怎么做到的呢?
    将一条消息发给对象时,如果并不知道对方的具体类型是什么,但采取的行
    动同样是正确的,这种情况就叫作“多形性” (Polymorphism) 。对面向对象的程
    序设计语言来说,它们用以实现多形性的方法叫作“动态绑定” 。编译器和运行
    期系统会负责对所有细节的控制;我们只需知道会发生什么事情,而且更重要的
    是,如何利用它帮助自己设计程序。

    从这段文字理解:

    动态绑定 :是实现多态的技术

    衍生类 :继承类或者子类

    基础类 :父类

    什么是多态

    面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。

    多态的定义:同一类所有对象都能接收相同的消息,允许衍生类对象对同一消息做出响应。即同一消息可以根据发送对象(衍生对象)的不同而采用多种不同的行为方式。(发送消息就是函数调用)

    实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象(实际衍生对象)的实际类型,根据其实际的类型调用其相应的方法。

    多态的作用:消除类型之间的耦合关系。

    多态存在的三个必要条件
    一、要有继承;
    二、要有重写;
    三、父类引用指向子类对象。

     多态的好处

    1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
    2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。
    3.接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
    4.灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
    5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

    Java中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。

    一个小题目:(从别处借过来的)

    class A ...{  
             public String show(D obj)...{  
                    return ("A and D");  
             }   
             public String show(A obj)...{  
                    return ("A and A");  
             }   
    }   
    class B extends A...{  
             public String show(B obj)...{  
                    return ("B and B");  
             }  
             public String show(A obj)...{  
                    return ("B and A");  
             }   
    }  
    class C extends B...{}   
    class D extends B...{}  
    (二)问题:以下输出结果是什么?
    A a1 = new A();  
            A a2 = new B();  
            B b = new B();  
            C c = new C();   
            D d = new D();   
            System.out.println(a1.show(b));   ①  
            System.out.println(a1.show(c));   ②  
            System.out.println(a1.show(d));   ③  
            System.out.println(a2.show(b));   ④  
            System.out.println(a2.show(c));   ⑤  
            System.out.println(a2.show(d));   ⑥  
            System.out.println(b.show(b));    ⑦  
            System.out.println(b.show(c));    ⑧  
            System.out.println(b.show(d));    ⑨   
    (三)答案
    ①   A and A
    ②   A and A
    ③   A and D
    ④   B and A
    ⑤   B and A
    ⑥   A and D
    ⑦   B and B
    ⑧   B and B
    ⑨   A and D
    输出注释:
    1.实例对象为A,参数为对象B,B为A的子类。执行A.class中show(A obj)
    2.同上
    3.实例对象为A,参数为对象D,执行A.class中show(D obj)
    4.实例对象依然为A,参数为B,本应执行A.class中show(A obj),但是,B.class重写了show(A obj),所以执行B.class show(A obj)
    5.同上
    6.执行A.class show(D obj) B中并没有重写。
    7,8.实例对象为B,参数为B或者B的子类,执行show(B obj)
    9.实例对象为B,参数为D,因为B继承自A,也可以执行A中的show(D obj)

    A a2 = new B(); 
    栈中的引用变量是A,堆中的实例变量是B。
    将子类的实例,赋值给父类的引用。就是向上转型。
    向上转型,在运行时,会遗忘子类对象中与父类对象中不同的方法。也会覆盖与父类中相同的方法--重写。(方法名,参数都相同)
    所以a2,可以调用的方法就是,A中有的,但是B中没有的方法,和B中的重写A的方法。

     解析:
    ①,②,③调用a1.show()方法,a1 属于A类,A类有两个方法show(D obj)和show(A obj)。①a1.show(b),参数b为A类的子类对象,这里为向上转型,相当于A obj=b;所以调用show(A obj)方法,得到A and A结果。②同理,③参数为d,调用show(D obj),得到A and D。
    ④,⑤,⑥调用a2.show()方法,A a2 = new B();是向上转型,所以对a2方法的调用,使用A1类的方法show(D obj)和show(A obj),但此时show(A obj)已经被重写为return ("B and A"), ④a2.show(b),调用show(A obj),得到B and A。⑤同理,⑥调用show(D obj),得到A and D。
    ⑦,⑧,⑨调用b.show()方法,b为B类,B类的show方法有继承的show(D obj),自己的两个show(B obj)、show(A obj)。
    ⑦调用show(B obj),得到B and B,⑧向上转型,调用show(B obj),得到B and B⑨show(D obj),得到A and D

  • 相关阅读:
    Java for LeetCode 229 Majority Element II
    Java for LeetCode 228 Summary Ranges
    Java for LeetCode 227 Basic Calculator II
    Java for LintCode 颜色分类
    Java for LintCode 链表插入排序
    Java for LintCode 颠倒整数
    Java for LintCode 验证二叉查找树
    Java for LeetCode 226 Invert Binary Tree
    Java for LeetCode 225 Implement Stack using Queues
    Java for LeetCode 224 Basic Calculator
  • 原文地址:https://www.cnblogs.com/ccgjava/p/6297107.html
Copyright © 2011-2022 走看看