在Java中,父类的变量可以引用父类的实例,也可以引用子类的实例。

请读者先看一段代码:

  1. public class Demo {
  2. public static void main(String[] args){
  3. Animal obj = new Animal();
  4. obj.cry();
  5. obj = new Cat();
  6. obj.cry();
  7. obj = new Dog();
  8. obj.cry();
  9. }
  10. }
  11. class Animal{
  12. // 动物的叫声
  13. public void cry(){
  14. System.out.println("不知道怎么叫");
  15. }
  16. }
  17. class Cat extends Animal{
  18. // 猫的叫声
  19. public void cry(){
  20. System.out.println("喵喵~");
  21. }
  22. }
  23. class Dog extends Animal{
  24. // 狗的叫声
  25. public void cry(){
  26. System.out.println("汪汪~");
  27. }
  28. }

运行结果:
不知道怎么叫
喵喵~
汪汪~

上面的代码,定义了三个类,分别是 Animal、Cat 和 Dog,Cat 和 Dog 类都继承自 Animal 类。obj 变量的类型为 Animal,它既可以指向 Animal 类的实例,也可以指向 Cat 和 Dog 类的实例,这是正确的。也就是说,父类的变量可以引用父类的实例,也可以引用子类的实例。注意反过来是错误的,因为所有的猫都是动物,但不是所有的动物都是猫。

可以看出,obj 既可以是人类,也可以是猫、狗,它有不同的表现形式,这就被称为多态。多态是指一个事物有不同的表现形式或形态。

再比如“人类”,也有很多不同的表达或实现,TA 可以是司机、教师、医生等,你憎恨自己的时候会说“下辈子重新做人”,那么你下辈子成为司机、教师、医生都可以,我们就说“人类”具备了多态性。

多态存在的三个必要条件:要有继承、要有重写、父类变量引用子类对象。

当使用多态方式调用方法时:

  • 首先检查父类中是否有该方法,如果没有,则编译错误;如果有,则检查子类是否覆盖了该方法。
  • 如果子类覆盖了该方法,就调用子类的方法,否则调用父类方法。

从上面的例子可以看出,多态的一个好处是:当子类比较多时,也不需要定义多个变量,可以只定义一个父类类型的变量来引用不同子类的实例。请再看下面的一个例子:

  1. public class Demo {
  2. public static void main(String[] args){
  3. // 借助多态,主人可以给很多动物喂食
  4. Master ma = new Master();
  5. ma.feed(new Animal(), new Food());
  6. ma.feed(new Cat(), new Fish());
  7. ma.feed(new Dog(), new Bone());
  8. }
  9. }
  10. // Animal类及其子类
  11. class Animal{
  12. public void eat(Food f){
  13. System.out.println("我是一个小动物,正在吃" + f.getFood());
  14. }
  15. }
  16. class Cat extends Animal{
  17. public void eat(Food f){
  18. System.out.println("我是一只小猫咪,正在吃" + f.getFood());
  19. }
  20. }
  21. class Dog extends Animal{
  22. public void eat(Food f){
  23. System.out.println("我是一只狗狗,正在吃" + f.getFood());
  24. }
  25. }
  26. // Food及其子类
  27. class Food{
  28. public String getFood(){
  29. return "事物";
  30. }
  31. }
  32. class Fish extends Food{
  33. public String getFood(){
  34. return "鱼";
  35. }
  36. }
  37. class Bone extends Food{
  38. public String getFood www.zenmebanw.com (){
  39. return "骨头";
  40. }
  41. }
  42. // Master类
  43. class Master{
  44. public void feed(Animal an, Food f){
  45. an.eat(f);
  46. }
  47. }

运行结果:
我是一个小动物,正在吃事物
我是一只小猫咪,正在吃鱼
我是一只狗狗,正在吃骨头

Master 类的 feed 方法有两个参数,分别是 Animal 类型和 Food 类型,因为是父类,所以可以将子类的实例传递给它,这样 Master 类就不需要多个方法来给不同的动物喂食。

动态绑定

为了理解多态的本质,下面讲一下Java调用方法的详细流程。

1) 编译器查看对象的声明类型和方法名。

假设调用 obj.func(param),obj 为 Cat 类的对象。需要注意的是,有可能存在多个名字为func但参数签名不一样的方法。例如,可能存在方法 func(int) 和 func(String)。编译器将会一一列举所有 Cat 类中名为func的方法和其父类 Animal 中访问属性为 public 且名为func的方法。

这样,编译器就获得了所有可能被调用的候选方法列表。

2) 接下来,编泽器将检查调用方法时提供的参数签名。

如果在所有名为func的方法中存在一个与提供的参数签名完全匹配的方法,那么就选择这个方法。这个过程被称为重载解析(overloading resolution)。例如,如果调用 func("hello"),编译器会选择 func(String),而不是 func(int)。由于自动类型转换的存在,例如 int 可以转换为 double,如果没有找到与调用方法参数签名相同的方法,就进行类型转换后再继续查找,如果最终没有匹配的类型或者有多个方法与之匹配,那么编译错误。

这样,编译器就获得了需要调用的方法名字和参数签名。

3) 如果方法的修饰符是private、static、final(static和final将在后续讲解),或者是构造方法,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式 称为静态绑定(static binding)。

与此对应的是,调用的方法依赖于对象的实际类型, 并在运行时实现动态绑。例如调用 func("hello"),编泽器将采用动态绑定的方式生成一条调用 func(String) 的指令。

4)当程序运行,并且釆用动态绑定调用方法时,JVM一定会调用与 obj 所引用对象的实际类型最合适的那个类的方法。我们已经假设 obj 的实际类型是 Cat,它是 Animal 的子类,如果 Cat 中定义了 func(String),就调用它,否则将在 Animal 类及其父类中寻找。

每次调用方法都要进行搜索,时间开销相当大,因此,JVM预先为每个类创建了一个方法表(method lable),其中列出了所有方法的名称、参数签名和所属的类。这样一来,在真正调用方法的时候,虚拟机仅查找这个表就行了。在上面的例子中,JVM 搜索 Cat 类的方法表,以便寻找与调用 func("hello") 相匹配的方法。这个方法既有可能是 Cat.func(String),也有可能是 Animal.func(String)。注意,如果调用super.func("hello"),编译器将对父类的方法表迸行搜索。

假设 Animal 类包含cry()、getName()、getAge() 三个方法,那么它的方法表如下:
cry() -> Animal.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()

实际上,Animal 也有默认的父类 Object(后续会讲解),会继承 Object 的方法,所以上面列举的方法并不完整。

假设 Cat 类覆盖了 Animal 类中的 cry() 方法,并且新增了一个方法 climbTree(),那么它的参数列表为:
cry() -> Cat.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()
climbTree() -> Cat.climbTree()

在运行的时候,调用 obj.cry() 方法的过程如下:

    • JVM 首先访问 obj 的实际类型的方法表,可能是 Animal 类的方法表,也可能是 Cat 类及其子类的方法表。
    • JVM 在方法表中搜索与 cry() 匹配的方法,找到后,就知道它属于哪个类了。
    • JVM 调用该方法。

四. Java继承和多态4. 多态和动态绑定的更多相关文章

  1. 四. Java继承和多态8.Java final关键字:阻止继承和多态

    在 Java 中,声明类.变量和方法时,可使用关键字 final 来修饰.final 所修饰的数据具有“终态”的特征,表示“最终的”意思.具体规定如下: final 修饰的类不能被继承. final ...

  2. 四. Java继承和多态10. Java Object类

    Object 类位于 java.lang 包中,是所有 Java 类的祖先,Java 中的每个类都由它扩展而来. 定义Java类时如果没有显示的指明父类,那么就默认继承了 Object 类.例如: p ...

  3. 四. Java继承和多态6. 多态对象的类型转换

    这里所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象.当对不存在继承关系的对象进行强制类型转换时,java 运行时将抛出 java.lang.ClassCastException 异常. ...

  4. 四. Java继承和多态3. 继承中的方法的覆盖和重载

    在类继承中,子类可以修改从父类继承来的方法,也就是说子类能创建一个与父类方法有不同功能的方法,但具有相同的名称.返回值类型.参数列表. 如果在新类中定义一个方法,其名称.返回值类型和参数列表正好与父类 ...

  5. 四. Java继承和多态1. 继承的概念与实现

    继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承(例如儿子继承父亲财产)类似. 继承可以理解为一个类从另一个类获取方法和属性的过程.如果类B继承于类A,那么B就拥有A的方法和属性. ...

  6. 四. Java继承和多态9. 类与类之间的关系

    类与类之间最常见的关系主要有三种:依赖(uses-a).聚合(has-a)和继承(is-a). 下面以在线书店订单系统为例,来详细的讲述这三种关系的概念. 在线书店订单系统的主要功能是:注册用户可以登 ...

  7. 四. Java继承和多态7. Java static关键字

    static 修饰符能够与变量.方法一起使用,表示是“静态”的. 静态变量和静态方法能够通过类名来访问,不需要创建一个类的对象来访问该类的静态成员,所以static修饰的成员又称作类变量和类方法.静态 ...

  8. 四. Java继承和多态5. instanceof 运算符

    多态性带来了一个问题,就是如何判断一个变量所实际引用的对象的类型 . C++使用runtime-type information(RTTI),Java 使用 instanceof 操作符. insta ...

  9. 四. Java继承和多态2. Java super关键字

    super 关键字与 this 类似,this 用来表示当前类的实例,super 用来表示父类. super 可以用在子类中,通过点号(.)来获取父类的成员变量和方法.super 也可以用在子类的子类 ...

随机推荐

  1. Python全栈工程师(列表、拷贝)

    ParisGabriel     感谢 大家的支持  你们的阅读评价就是我最好的更新动力  我会坚持吧排版做的越来越好      每天坚持 一天一篇 点个订阅吧  灰常感谢    当个死粉也阔以 Py ...

  2. sources-x.list

    deb http://debian.ustc.edu.cn/ubuntu/ xenial main multiverse restricted universe deb http://debian.u ...

  3. Python namedtuple(命名元组)使用实例

    Python namedtuple(命名元组)使用实例 #!/usr/bin/python3 import collections MyTupleClass = collections.namedtu ...

  4. HDU 4763 Theme Section ( KMP next函数应用 )

    设串为str, 串长为len. 对整个串求一遍next函数,从串结尾开始顺着next函数往前找<=len/3的最长串,假设串长为ans,由于next的性质,所以找到的串肯定满足E……E这种形式, ...

  5. POJ A-Wireless Network

    http://poj.org/problem?id=2236 An earthquake takes place in Southeast Asia. The ACM (Asia Cooperated ...

  6. canvas 基础

    1.<canvas>元素 <canvas id="tutorial" width="150" height="150"&g ...

  7. 清除浮动float (:after方法)

    1. 什么时候需要清除浮动?清除浮动有哪些方法? (1)对元素进行了浮动(float)后,该元素就会脱离文档流,浮动在文档之上.在CSS中,任何元素都可以浮动.浮动元素会生成一个块级框,而不论它本身是 ...

  8. 【bzoj4146】[AMPPZ2014]Divisors 数论

    原文地址:http://www.cnblogs.com/GXZlegend/p/6801411.html 题目描述 给定一个序列a[1],a[2],...,a[n].求满足i!=j且a[i]|a[j] ...

  9. 【bzoj3289】Mato的文件管理 离散化+莫队算法+树状数组

    原文地址:http://www.cnblogs.com/GXZlegend/p/6805224.html 题目描述 Mato同学从各路神犇以各种方式(你们懂的)收集了许多资料,这些资料一共有n份,每份 ...

  10. 比较运算符compareTo()、equals()、==之间的区别与应用总结

    在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配 ...