面向对象编程(十四)——面向对象三大特性之多态②

面向对象最核心的机制——动态绑定,也叫多态。

通过下面的例子理解动态绑定,即多态

  1 package javastudy.summary;
  2
  3 class Animal {
  4     /**
  5      * 声明一个私有的成员变量name。
  6      */
  7     private String name;
  8
  9     /**
 10      * 在Animal类自定义的构造方法
 11      * @param name
 12      */
 13     Animal(String name) {
 14         this.name = name;
 15     }
 16
 17     /**
 18      * 在Animal类里面自定义一个方法enjoy
 19      */
 20     public void enjoy() {
 21         System.out.println("动物的叫声……");
 22     }
 23 }
 24
 25 /**
 26  * 子类Cat从父类Animal继承下来,Cat类拥有了Animal类所有的属性和方法。
 27  * @author gacl
 28  *
 29  */
 30 class Cat extends Animal {
 31     /**
 32      * 在子类Cat里面定义自己的私有成员变量
 33      */
 34     private String eyesColor;
 35
 36     /**
 37      * 在子类Cat里面定义Cat类的构造方法
 38      * @param n
 39      * @param c
 40      */
 41     Cat(String n, String c) {
 42         /**
 43          * 在构造方法的实现里面首先使用super调用父类Animal的构造方法Animal(String name)。
 44          * 把子类对象里面的父类对象先造出来。
 45          */
 46         super(n);
 47         eyesColor = c;
 48     }
 49
 50     /**
 51      * 子类Cat对从父类Animal继承下来的enjoy方法不满意,在这里重写了enjoy方法。
 52      */
 53     public void enjoy() {
 54         System.out.println("我养的猫高兴地叫了一声……");
 55     }
 56 }
 57
 58 /**
 59  * 子类Dog从父类Animal继承下来,Dog类拥有了Animal类所有的属性和方法。
 60  * @author gacl
 61  *
 62  */
 63 class Dog extends Animal {
 64     /**
 65      * 在子类Dog里面定义自己的私有成员变量
 66      */
 67     private String furColor;
 68
 69     /**
 70      * 在子类Dog里面定义Dog类的构造方法
 71      * @param n
 72      * @param c
 73      */
 74     Dog(String n, String c) {
 75         /**
 76          * 在构造方法的实现里面首先使用super调用父类Animal的构造方法Animal(String name)。
 77          * 把子类对象里面的父类对象先造出来。
 78          */
 79         super(n);
 80         furColor = c;
 81     }
 82
 83     /**
 84      * 子类Dog对从父类Animal继承下来的enjoy方法不满意,在这里重写了enjoy方法。
 85      */
 86     public void enjoy() {
 87         System.out.println("我养的狗高兴地叫了一声……");
 88     }
 89 }
 90
 91 /**
 92  * 子类Bird从父类Animal继承下来,Bird类拥有Animal类所有的属性和方法
 93  * @author gacl
 94  *
 95  */
 96 class Bird extends Animal {
 97     /**
 98      * 在子类Bird里面定义Bird类的构造方法
 99      */
100     Bird() {
101         /**
102          * 在构造方法的实现里面首先使用super调用父类Animal的构造方法Animal(String name)。
103          * 把子类对象里面的父类对象先造出来。
104          */
105         super("bird");
106     }
107
108     /**
109      * 子类Bird对从父类Animal继承下来的enjoy方法不满意,在这里重写了enjoy方法。
110      */
111     public void enjoy() {
112         System.out.println("我养的鸟高兴地叫了一声……");
113     }
114 }
115
116 /**
117  * 定义一个类Lady(女士)
118  * @author gacl
119  *
120  */
121 class Lady {
122     /**
123      * 定义Lady类的私有成员变量name和pet
124      */
125     private String name;
126     private Animal pet;
127
128     /**
129      * 在Lady类里面定义自己的构造方法Lady(),
130      * 这个构造方法有两个参数,分别为String类型的name和Animal类型的pet,
131      * 这里的第二个参数设置成Animal类型可以给我们的程序带来最大的灵活性,
132      * 因为作为养宠物来说,可以养猫,养狗,养鸟,只要是你喜欢的都可以养,
133      * 因此把它设置为父类对象的引用最为灵活。
134      * 因为这个Animal类型的参数是父类对象的引用类型,因此当我们传参数的时候,
135      * 可以把这个父类的子类对象传过去,即传Dog、Cat和Bird等都可以。
136      * @param name
137      * @param pet
138      */
139     Lady(String name, Animal pet) {
140         this.name = name;
141         this.pet = pet;
142     }
143
144     /**
145      * 在Lady类里面自定义一个方法myPetEnjoy()
146      * 方法体内是让Lady对象养的宠物自己调用自己的enjoy()方法发出自己的叫声。
147      */
148     public void myPetEnjoy() {
149         pet.enjoy();
150     }
151 }
152
153 public class TestPolymoph {
154     public static void main(String args[]) {
155         /**
156          * 在堆内存里面new了一只蓝猫对象出来,这个蓝猫对象里面包含有一个父类对象Animal。
157          */
158         Cat c = new Cat("Catname", "blue");
159         /**
160          * 在堆内存里面new了一只黑狗对象出来,这个黑狗对象里面包含有一个父类对象Animal。
161          */
162         Dog d = new Dog("Dogname", "black");
163         /**
164          * 在堆内存里面new了一只小鸟对象出来,这个小鸟对象里面包含有一个父类对象Animal。
165          */
166         Bird b = new Bird();
167
168         /**
169          * 在堆内存里面new出来3个小姑娘,名字分别是l1,l2,l3。
170          * l1养了一只宠物是c(Cat),l2养了一只宠物是d(Dog),l3养了一只宠物是b(Bird)。
171          * 注意:调用Lady类的构造方法时,传递过来的c,d,b是当成Animal来传递的,
172          * 因此使用c,d,b这三个引用对象只能访问父类Animal里面的enjoy()方法。
173          */
174         Lady l1 = new Lady("l1", c);
175         Lady l2 = new Lady("l2", d);
176         Lady l3 = new Lady("l3", b);
177         /**
178          * 这三个小姑娘都调用myPetEnjoy()方法使自己养的宠物高兴地叫起来。
179          */
180         l1.myPetEnjoy();
181         l2.myPetEnjoy();
182         l3.myPetEnjoy();
183     }
184 }

运行结果:

  

画内存图理解动态绑定(多态)

首先从main方法的第一句话开始分析:

    Cat c = new Cat("Catname","blue");

  程序执行到这里,栈空间里有一个变量c,c里面装着一系列的值,通过这些值可以找到位于堆内存里面new出来的Cat对象。因此c是Cat对象的一个引用,通过c可以看到这个Cat对象的全部。c指向new出来的Cat对象。在new这个Cat对象的时候,调用了Cat对象的构造方法Cat(String n,String c),定义如下:

    Cat(String n,String c){

      super(n);

      eyesColor=c;

    }

  因此在构造子类对象时首先使用父类对象的引用super调用父类的构造方法Animal(String name),定义如下:

    Animal(String name){

      this.name=name;

    }

  因此会把传过来的字符串“Catname”传递给父类对象的name属性。当Cat(String n,String c)构造方法调用结束后,真真正正在堆内存里面new出了一只Cat,这只Cat里面包含有父类对象Animal,这个Animal对象有自己的属性name,name属性的值为调用父类构造方法时传递过来的字符串Catname。除此之外,这只Cat还有自己的私有成员变量eyesColor,eyesColor属性的属性值为调用子类构造方法时传递过来的字符串blue。所以执行完这句话以后,内存中的布局是栈内存里面有一个引用c,c指向堆内存里面new出来的一只Cat,而这只Cat对象里面又包含有父类对象Animal,Animal对象有自己的属性name,属性值为Catname,Cat除了拥有从Animal类继承下来的name属性外,还拥有一个自己私有的属性eyesColor,属性值为blue。这就是执行完第一句话以后整个内存布局的情况如下图所示:

  

接着看这句话:Lady l1 = new Lady(“l1”,c);

  

  程序执行到这里,首先在栈内存里面多了一个引用变量l1,l1里面装着一个值,通过这个值可以找到在堆内存里面new出来的Lady对象。l1就是这个Lady对象的引用,l1指向Lady对象。在创建Lady对象时,调用Lady类的构造方法:Lady(String name,Animal pet),其定义如下:

  Lady(String name,Animal pet){

    this.name=name;

    this.pet=pet;

  }

这个构造方法有两个参数,分别是String类型的name和Animal类型的pet,pet参数是一个父类对象的引用类型,这里把l1和c作为实参传递给了构造方法,接着在构造方法里面执行this.name=name,把传递过来的l1由传给Lady对象的name属性,因此Lady对象的name属性值为l1,这里也把前面new出来的那只Cat的引用c传递给了构造方法里面的参数pet,接着在构造方法里面执行this.pet=pet,pet参数又把c传过来的内容传递给Lady对象的pet属性,因此pet属性的属性值就是可以找到Cat对象的地址,因此Lady对象的pet属性也成为了Cat对象的引用对象了,通过pet里面装着的值是可以找到Cat对象的,因此pet也指向了Cat,但并不是全部指向Cat,pet指向的只是位于Cat对象内部的Animal对象,这是因为在调用构造方法时,是把c当成一个Animal对象的引用传过来的,把c作为一个Animal对象传递给了pet,所以得到的pet也是一个Animal对象的引用,因此这个pet引用指向的只能是位于Cat对象里面的Animal对象。在我pet引用对象眼里,你Cat对象就是一只普通的Animal,访问你的时候只能访问得到你里面的name属性,而你的eyesColor属性我是访问不到的,我能访问到你的name属性,访问的是位于你内部里面的父对象的name属性,因为我pet引用本身就是一个父类对象的引用,因此我可以访问父类对象的全部属性,而你子类对象Cat自己新增加的成员我pet引用是访问不了的。不过现在我pet引用不去访问你父类对象的成员变量name了,而是去访问你的成员方法enjoy了。首先是使用Lady对象的引用l1去调用Lady对象的myPetEnjoy()方法,myPetEnjoy()方法定义如下:

  public void myPetEnjoy(){

    pet.enjoy();

  }

  然后在myPetEnjoy()方法体里面又使用pet引用对象去调用父类对象里面的enjoy方法。

方法是放在代码区(code seg)里面的,里面的方法就是一句句代码。因此当使用pet引用去访问父类对象的方法时,首先是找到这个父类对象,然后看看它里面的方法到底在哪里存着,找到那个方法再去执行。这里头就比较有意思了,code seg里面有很多个enjoy方法,有父类的enjoy()方法,也有子类重写了从父类继续下来的enjoy()方法,那么调用的时候到底调用的是哪一个呢?是根据谁来确定呢?注意:这是根据你实际当中的对象来确定的,你实际当中new出来的是谁,就调用谁的enjoy方法,当你找这个方法的时候,通过pet引用能找得到这个方法,但调用代码区里面的哪一个enjoy方法不是通过引用类型来确定的,如果是通过引用类型pet来确定,那么调用的肯定是Animal的enjoy()方法,可是现在是根据实际的类型来确定,我们的程序运行以后才在堆内存里面创建出一只Cat,然后根据你实际当中new出来的类型来判断我到底应该调用哪一个enjoy()方法。如果是根据实际类型,那么调用的就应该是Cat的enjoy()方法。如果是根据引用类型,那么调用的就应该是Animal的enjoy()方法。现在动态绑定这种机制指的是实际当中new的是什么类型,就调用谁的enjoy方法。所以说虽然你是根据我父类里面的enjoy方法来调用,可是实际当中却是你new的是谁调用的就是谁的enjoy()方法。即实际当中调用的却是子类里面重写后的那个enjoy方法。当然,讲一点更深的机制,你实际当中找这个enjoy方法的时候,在父类对象的内部有一个enjoy方法的指针,指针指向代码区里面父类的Animal的enjoy方法,只不过当你new这个对象的时候,这个指针随之改变,你new的是什么对象,这个指针就指向这个对象重写后的那个enjoy方法,所以这就叫做动态绑定。只有在动起来的时候,也就是在程序运行期间,new出了这个对象了以后你才能确定到底要调用哪一个方法。我实际当中的地址才会绑定到相应的方法的地址上面,所以叫动态绑定。调这个方法的时候,只要你这个方法重写了,实际当中调哪一个,要看你实际当中new的是哪个对象,这就叫多态,也叫动态绑定。动态绑定带来莫大的好处是使程序的可扩展性达到了最好,我们原来做这个可扩展性的时候,首先都是要在方法里面判断一下这只动物是哪一类里面的动物,通过if (object instanceof class)这样的条件来判断这个new出来的对象到底是属于哪一个类里面的,如果是一只猫,就调用猫的enjoy方法,如果是一条狗,就调用狗的enjoy方法。如果我现在增加了一个Bird类,那么扩展的时候,你又得在方法里面写判断这只鸟属于哪一个类然后才能调用这只鸟的enjoy方法。每增加一个对象,你都要在方法里面增加一段判断这个对象到底属于哪个类里面的代码然后才能执行这个对象相应的方法。即每增加一个新的对象,都要改变方法里面的处理代码,而现在,你不需要再改变方法里面的处理代码了,因为有了动态绑定。你要增加哪一个对象,你实际当中把这个对象new出来就完了,不再用去修改对象的处理方法里面的代码了。也就是当你实际当中要增加别的东西的时候,很简单,你直接加上去就成了,不用去改原来的结构,你要在你们家大楼的旁边盖一个厨房,很简单,直接在旁边一盖就行了,大楼的主要支柱什么的你都不用动,这就可以让可扩展性达到了极致,这就为将来的可扩展打下了基础,也只有动态绑定(多态)这种机制能帮助我们做到这一点——让程序的可扩展性达到极致。因此动态绑定是面向对象的核心,如果没有动态绑定,那么面向对象绝对不可能发展得像现在这么流行,所以动态绑定是面向对象核心中的核心。

  总结动态绑定(多态):动态绑定是指在“执行期间”(而非编译期间)判断所引用的实际对象类型,根据其实际的类型调用其相应的方法。所以实际当中找要调用的方法时是动态的去找的,new的是谁就找谁的方法,这就叫动态绑定。动态绑定帮助我们的程序的可扩展性达到了极致。

多态的存在有三个必要的条件:

  1. 要有继承(两个类之间存在继承关系,子类继承父类)
  2. 要有重写(在子类里面重写从父类继承下来的方法)
  3. 父类引用指向子类对象

  这三个条件一旦满足,当你调用父类里面被重写的方法的时候,实际当中new的是哪个子类对象,就调用子类对象的方法(这个方法是从父类继承下来后重写后的方法)。

  面向对象比较强调类和类之间,对象和对象之间的一种组织关系,如果能把这种组织关系组织得比较好的话,你的程序想扩展性比较好,比较健壮,维护性比较好这些都可以达到,关键看你的设计到底好还是不好。

相关链接:

java基础学习总结——多态(动态绑定)

面向对象编程(十四)——面向对象三大特性之多态①

时间: 2024-10-05 22:00:55

面向对象编程(十四)——面向对象三大特性之多态②的相关文章

[.net 面向对象编程基础] (11) 面向对象三大特性——封装

[.net 面向对象编程基础] (11) 面向对象三大特性——封装 我们的课题是面向对象编程,前面主要介绍了面向对象的基础知识,而从这里开始才是面向对象的核心部分,即 面向对象的三大特性:封装.继承.多态. 1.封装概念 封装:每个对象都包含有它能进行操作的所有信息,这个特性称为封装.这样的方法包含在类中,通过类的实例来实现. 2.封装的优点 A.良好的封装能够减少耦合(比如实现界面和逻辑分离) B.可以让类对外接口不变,内部可以实现自由的修改 C.类具有清晰的对外接口,使用者只需调用,无需关心

面向对象的三大特性之多态

# 面向对象的三大特性之多态 # 多态 由不同的类实例化得到的对象,调用同一个方法,执行的逻辑不同 # 多态的概念指出了对象如何通过他们的属性和动作来操作及访问,而不需要考虑他们具体的类 # 多态表明了动态(运行时)绑定的存在,允许重载及运行时类型确定和验证 # 多态是由同一个类实例化出的多个对象,这些对象执行方法时,执行的过程和结果是不一样的 class H2O: # 定义了一个水的基类 def __init__(self, name, temp): # 初始化实例属性 self.name =

java面向对象编程——第四章 类和对象

OO:面向对象 OOP:面向对象编程 OOA:面向对象分析 OOD:面向对象设计 结构化编程:从顶向下,将一个大问题分解成更小的任务,然后为每一个更小的任务编写一个过程.最后程序员会编写一个主过程来启动程序流程,随后根据程序流程走向,调用想要的其它过程. 对象是存在的具体实体,具有明确定义的特征和行为. 万物皆为对象,对象因我关注而产生. 面向对象:一种认识事物的方式,注重对事物整体的认知,最符合人类自然的思维习惯. 对象是数据封装的结果. 类是具有相同属性和行为的一组对象的集合. 在软件系统中

java提高篇(四)_理解java的三大特性之多态 转自 http://cmsblogs.com

多态就是指程序中定义 的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该 引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定.因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让 引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个 运行状态,这就是多态性 一. 向上转型

java面向对象编程(七)--四大特征之多态

1.多态概念 多态性是对象多种表现形式的体现.比如我们说"宠物"这个对象,它就有很多不同的表达或实现,比如有小猫.小狗.蜥蜴等等.那么我到宠物店说"请给我一只宠物",服务员给我小猫.小狗或者蜥蜴都可以,我们就说"宠物"这个对象就具备多态性. java中的多态,就是指一个引用(类型)在不同情况下的多种状态.也可以理解成,多态是指通过指向父类的指针,来调用在不同子类中实现的方法.也可以理解为"一个接口,多个方法". 实现多态有两种

[转]理解java的三大特性之多态

java提高篇(四)-----理解java的三大特性之多态 面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承是为了重用父类代码.两个类若存在IS-A的关系就可以使用继承.,同时继承也为实现多态做了铺垫.那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开: 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在

(一)Python入门-6面向对象编程:07面向对象三大特征(封装、继承、多态)-继承

一:面向对象三大特征介绍 Python是面向对象的语言,也支持面向对象编程的三大特性:继承.封装(隐藏).多态. 封装(隐藏) 隐藏对象的属性和实现细节,只对外提供必要的方法.相当于将“细节封装起来”,只 对外暴露“相关调用方法”. 通过前面学习的“私有属性.私有方法”的方式,实现“封装”.Python 追求简洁的语法,没有严格的语法级别的“访问控制符”,更多的是依靠程序员自觉实现. 继承 继承可以让子类具有父类的特性,提高了代码的重用性. 从设计上是一种增量进化,原有父类设计不变的情况下,可以

Java中面向对象三大特性之——多态

多态的概述:  多态是继封装.继承之后,面向对象的第三大特性. 生活中,比如跑的动作,小猫.小狗和大象,跑起来是不一样的.再比如飞的动作,昆虫.鸟类和飞机,飞起来也是不一样的.可见,同一行为,通过不同的事物,可以体现出来的不同的形态.多态,描述的就是这样的状态. 多态的条件 1.继承 2.方法的重写 (为了让多态有意义) 3.父类的引用指向子类的对象 多态的体现 父类类型 变量名 = new 子类对象: 变量名.方法名(); 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译

面向对象编程(十)——继承之Super关键字及内存分析

阅读目录 Super关键字画内存分析图了解程序执行的整个过程 Super关键字 在JAVA类中使用super来引用父类的成分,用this来引用当前对象,如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象.怎么去引用里面的父类对象呢?使用super来引用,this指的是当前对象的引用,super是当前对象里面的父对象的引用. super是直接父类对象的引用.可以通过super来访问父类中被子类覆盖的方法或属性. (注意和this的区别:this是当前对