java 继承之上——动绑机制详解

简介: java 动态绑定机制分享,本篇博文为java 面向对象三大特性——多态篇的内容补充。

一、前言 :

在java 面向对象三大特性——继承篇中,我们说过java 中查找方法的顺序为 : 本类方法—>父类方法—>更高一级的父类—>......Object(顶层父类) 然而,在某些情况下,这样的原则也会被凌驾。今天我们要说的java动态绑定机制,就是这样一个区别于继承机制的例外。

二、特点 :

  1. 通过对象的形式调用方法时,该方法会和堆内存中真正的该对象——的内存地址绑定,且这种绑定关系贯穿方法的执行全过程。这就是所谓的“动态绑定机制”。
  2. 当通过对象的形式调用属性时,不存在动态绑定机制,符合继承机制——即java中查找变量的顺序 : 局部变量—>成员变量—>父类—>更高的父类—>......—>Object

三、演示 :

1.准备工作 :

up以Father类为父类,Son类为子类(仅用作演示,无实际意义)。以TestBinding为测试类。还是老规矩,为了代码简洁,up这次将Father类和Son类写在了TestBinding类的源文件中。(PS : 其实不应该把测试类和子父类放一块儿的,但是动绑机制需要更直观地对比才好理解,就先这么干了)。

注意,我们要怎么测试动态绑定机制呢?别急,先来看看代码情况 :

up首先在Father类和Son类定义同名的成员变量temp,并且在子父类中各自给出temp变量的获取方法——即temp的getter方法——getTemp();。然后,分别在父类和子类定义两个关于temp变量的计算方法add_temp() add_temp_EX() ,相当于Son类重写了父类这两个方法。其中 : 父类的add_temp() 方法子类的add_temp_EX()方法中要调用getTemp()方法。最后,在测试类中利用多态的方式调用这两个方法。

TestBinding类,Father类,以及Son类代码如下 :

packageknowledge.polymorphism.auto_bind;
publicclassTestBinding {      /** 测试类 */publicstaticvoidmain(String[] args) {
//建立多态关系Fatherfather=newSon();
System.out.println(father.add_temp());
System.out.println(father.add_temp_EX());
    }
}
classFather {                  /** 父类 *///父类的成员变量inttemp=5;
//父类temp变量的getter方法publicintgetTemp() {
returntemp;
    }
//父类的两个关于计算temp变量的成员方法//方法一publicintadd_temp() {
returngetTemp() +10;
    }
//方法二publicintadd_temp_EX() {
returntemp+10;
    }
}
classSonextendsFather {      /** 子类 *///子类的成员变量(与父类成员变量同名)inttemp=11;
//子类temp变量的getter方法publicintgetTemp() {
returntemp;
    }
//子类的两个关于计算temp变量的成员方法//方法一publicintadd_temp() {
returntemp+100;
    }
//方法二publicintadd_temp_EX() {
returngetTemp() +1000;
    }
}

2.开始测试 :

在测试类中,有两条输出语句,是通过父类引用调用成员方法。我们知道,根据多态中成员方法的使用规则——编译看左,运行看左父类引用,说明编译类型是父类类型,而父类中定义了这两个方法,因此编译没问题,可调用;又因为多态关系——父类引用此时指向了子类对象,因此运行类型为子类,子类重写了父类的这两个方法,当然要优先调用子类中的方法

所以我们来看,当前情况下,子类add_temp() 方法返回的是temp + 100,根据java中属性的查找原则,现在该方法内并没有定义temp局部变量,且子类定义了自己的temp成员变量,因此返回的“temp + 10”中,temp = 11。所以调用第一个方法最后的返回结果是111

调用第二个方法 : 同理,子类add_temp()_EX 方法返回的是getTemp() + 1000,本类getTemp() 方法又返回了本类的temp变量。所以调用第二个方法最后的返回结果是1011

运行结果如下 :

image.png

看完演示①之后,可能就要有p小将personable小将,指风度翩翩的人)出来(挑刺儿)说理了 : 这tm(题目)演示的不就是继承篇讲得那一回事儿吗,看不出哪儿来的动态绑定机制😅

p小将你先别急,讲东西总是要铺垫的嘛。这不就来了?现在,我们在演示①的基础上,注释掉子类的add_temp() 方法,如下图所示 :

image.png

大家注意,本来我是优先调用子类的add_temp() 方法,但是现在它被注释掉了,没法儿用!因此,根据继承机制,现在要去调用父类的add_temp() 方法。up先把父类和子类的代码截图放下面,方便大家思考,就不用往上翻了,如下图 :

image.png

没错,现在我们要去调用父类的add_temp() 方法了,但是问题来了 : 父类的add_temp() 方法中调用了getTemp() 方法,那这时候的getTemp() 方法是用谁的呢?

这里就是一个初学者大概率犯错误的地方,如果没有了解过动态绑定机制,它们会想当然的认为 : 现在执行的是Father类中的add_temp() 方法,根据java中查找方法的原则,当然是使用Father类本类的getTemp() 方法了!所以,最后的返回值就是5 + 10 = 15,输出15。但是真的是如此吗 ?

运行结果如下 :

image.png

输出结果表明父类add_temp() 方法最后返回的不是5 + 10,而是11 + 10,这表明add_temp() 方法中调用的getTemp() 方法是子类中的getTemp() 方法为什么呢?

原因就是我们说的动态绑定机制,由于测试类中是通过father引用来调用方法,而它指向的对象是Son类对象。根据动态绑定机制,在调用add_temp() 方法时,father引用已经和它指向的对象绑定了,并且这种绑定关系会贯穿方法执行的全过程。因此,这里调用的getTemp() 方法是子类的方法,而不是父类的。

我们在演示②的基础下,把子类的add_temp_EX() 方法也给注释掉,如下图所示 :

image.png

与演示②同理,由于子类重写的add_temp_EX() 方法被注释掉,因此要使用父类的add_temp_EX() 方法。up还是将Father类代码和Son类代码的截图给大家放下面,就不用再往上翻了,如下图所示 :

image.png

那么问题又来了 : 此时调用add_temp_EX() 方法,返回的究竟是5 + 10呢,还是11 + 10呢?

相信大家现在就没啥疑问了,前面我们说了 : 调用属性时,不存在动态绑定机制,符合继承机制。因此,根据Java中查找属性的原则,这里的temp变量肯定是Father类本类的temp成员变量,也就是5。所以,最后输出的结果自然是5 + 10 = 15。

运行结果如下 :

image.png

四、练习 :

课堂练习 : (找朋友问题

1.现在有父类People类,以及它的子类Friends类,测试类为Find_friend类。与我们上述演示中的代码类似:People类和Friends类中各自定义了同名变量name,并赋予不同的初值子父类中分别给出name变量的获取方法——即name的getter方法——getName();。然后,分别在父类和子类给出两个不同的用于拼接字符串的方法find_friends() 和 find_friends_EX(),最后在测试类中建立People—friends类的多态关系,在输出语句中利用People类引用分别调用这两个方法, : 最后的输出结果分别是什么 ?

Find_friend类,People类,以及Friends类代码如下 :

packageknowledge.polymorphism.auto_bind;
publicclassFind_friend {          /** 测试类:找朋友类 */publicstaticvoidmain(String[] args) {
//多态Peoplepeople=newFriends();
System.out.println("普通版找朋友,找到了谁————"+people.make_friends());
System.out.println("牛逼版找朋友,找到了谁————"+people.make_friends_EX());
    }
}
classPeople {                      /** 父类:People类 *///父类的name变量Stringname="刻晴";
//父类name的getter方法publicStringgetName() {
returnname;
    }
//父类的两个拼接字符串方法//方法一publicStringmake_friends() {
returngetName() +" and "+"琪亚娜";
    }
//方法二publicStringmake_friends_EX() {
returnmake_friends() +" and "+"周杰伦";
    }
}
classFriendsextendsPeople {      /** 子类:Friends类 *///子类同名变量Stringname="吴京";
//子类name的getter方法publicStringgetName() {
returnname;
    }
//子类的两个拼接字符串方法(相当于重写了父类的这两个方法)//方法一publicStringmake_friends() {
returnsuper.getName();
    }
//方法二publicStringmake_friends_EX() {
returnsuper.make_friends_EX() +" and "+name;
    }
}

老规矩,up还是把子父类的代码截图给大家放下面,便于大家思考,如下图所示 :

image.png

答案 : (输出结果如下 : )

image.png

讲解 :

由于People类引用指向了Friends类对象,因此构成了People类—Friends类之间的多态关系。根据多态关系中成员方法的使用规则,编译类型是People类,而People类中定义了这两个找朋友方法,因此编译没问题,方法可调用;又因为子类重写了父类的两个找朋友方法,且没有被注释,因此优先使用Friends中的找朋友方法

来看第一个方法的调用首先找到子类的make_friends() 方法,返回的是super关键字指定的getName() 方法,即父类的getName() 方法;父类的getName() 方法中直接返回了name属性,而我们上面就讲了,调用属性时,不存在动态绑定机制,符合继承机制,父类定义了自己的name属性,因此getName() 方法最后返回的是"刻晴",所以make_friends() 方法最后返回的是"刻晴"

来看第二个方法的调用:首先找到子类的make_friends_EX()方法,返回的是父类的make_friends_EX() 方法返回的内容——加上一个字符串" and "——加上一个name变量。好滴,我们一个一个来看,父类的make_friends_EX() 方法的返回值与子类的make_friends_EX() 方法类似,也是一个字符串拼接的形式。但是注意,动态绑定机制来了。由于是通过对象的形式来调用的该方法,所以people引用会和堆内存中真正的Friends类对象的地址值绑定,并贯穿方法执行的全过程。

因此,父类的make_friends_EX() 方法中返回的第一个值——即make_friends() 方法的返回值——应该是调用子类的make_friends() 方法后的返回值,而子类的make_friends() 方法不就是我们刚才解释的第一个方法的调用么,返回的值是"刻晴"。因此,父类的make_friends_EX() 方法最后返回的值就是"刻晴 and 周杰伦"。我们再回到一开始的子类的make_friends_EX() 方法,name的值即本类的name属性的值"吴京"。所以make_friends_EX() 方法最后返回的是"刻晴 and 周杰伦 and 吴京"

五、结束:

🆗,我们的动绑机制就说到这里,大家一定要牢记动绑机制的两大特点感谢阅读!

最后再给大家抛出一个问题吧,在上面的练习中,如果注释掉子类的两个找朋友方法,那最后的输出结果分别又是什么呢?大家可以思考一下。

目录
相关文章
|
2月前
|
Java 程序员
Java中的继承和多态:理解面向对象编程的核心概念
【8月更文挑战第22天】在Java的世界中,继承和多态不仅仅是编程技巧,它们是构建可维护、可扩展软件架构的基石。通过本文,我们将深入探讨这两个概念,并揭示它们如何共同作用于面向对象编程(OOP)的实践之中。你将了解继承如何简化代码重用,以及多态如何为程序提供灵活性和扩展性。让我们启程,探索Java语言中这些强大特性的秘密。
|
13天前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
27天前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
28 9
Java——类与对象(继承和多态)
|
2天前
|
Java
为什么Java不支持多继承
本文讨论了Java不支持多继承的原因,包括避免菱形继承问题、简化编程语言和防止层次膨胀,同时提供了实现多继承效果的替代方案,如实现多接口、使用组合和继承加接口的方式。
6 0
为什么Java不支持多继承
|
15天前
|
Java
Java 的继承
在一个森林中,各种动物共存,如狗和猫。为了管理和组织这些动物,我们采用面向对象的方法设计模型。首先创建 `Animal` 超类,包含 `name` 和 `age` 属性及 `makeSound()` 和 `displayInfo()` 方法。接着,通过继承 `Animal` 创建子类 `Dog` 和 `Cat`,重写 `makeSound()` 方法以发出不同的声音。实例化这些子类并使用它们,展示了继承带来的代码重用、扩展性和多态性等优点。这种方式不仅简化了代码,还体现了现实世界的层次结构。
|
2月前
|
Java C++
【Java基础面试十七】、Java为什么是单继承,为什么不能多继承?
这篇文章讨论了Java单继承的设计原因,指出Java不支持多继承主要是为了避免方法名冲突等混淆问题,尽管Java类不能直接继承多个父类,但可以通过接口和继承链实现类似多继承的效果。
【Java基础面试十七】、Java为什么是单继承,为什么不能多继承?
|
1月前
|
Java
java的继承详解
在 Java 中,继承是一个核心概念,它允许子类 `extends` 父类来重用和扩展其属性与方法。子类可以覆盖父类的方法以改变行为,同时使用 `super` 关键字调用父类的构造方法或方法。虽然 Java 不支持多继承,但可以利用抽象类与接口实现多层继承。这种方式极大地增强了代码的复用性和维护性。
|
2月前
|
Java 编译器
Java继承
Java继承
17 1
|
2月前
|
Java
Java 新手入门:Java 封装、继承、多态详解
Java 新手入门:Java 封装、继承、多态详解
34 1
|
2月前
|
Java
【Java基础面试四十二】、 static修饰的类能不能被继承?
这篇文章讨论了Java中static关键字修饰的类是否可以被继承,解释了静态内部类的概念、规则以及如何实例化。