Java的动态绑定与双分派_动力节点Java学院整理
作者:zhengzhb 发布时间:2021-07-14 11:18:50
Java的动态绑定
所谓的动态绑定就是指程执行期间(而不是在编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。java继承体系中的覆盖就是动态绑定的,看一下如下的代码:
class Father {
public void method(){
System.out.println("This is Father's method");
}
}
class Son1 extends Father{
public void method(){
System.out.println("This is Son1's method");
}
}
class Son2 extends Father{
public void method(){
System.out.println("This is Son2's method");
}
}
public class Test {
public static void main(String[] args){
Father s1 = new Son1();
s1.method();
Father s2 = new Son2();
s2.method();
}
}
运行结果如下:
This is Son1's method
This is Son2's method
通过运行结果可以看到,尽管我们引用的类型是Father类型的,但是运行时却是调用的它实际类型(也就是Son1和Son2)的方法,这就是动态绑定。在java语言中,继承中的覆盖就是是动态绑定的,当我们用父类引用实例化子类时,会根据引用的实际类型调用相应的方法。
java的静态绑定
相对于动态绑定,静态绑定就是指在编译期就已经确定执行哪一个方法。在java中,方法的重载(方法名相同而参数不同)就是静态绑定的,重载时,执行哪一个方法在编译期就已经确定下来了。看一下代码:
class Father {}
class Son1 extends Father{}
class Son2 extends Father{}
class Execute {
public void method(Father father){
System.out.println("This is Father's method");
}
public void method(Son1 son){
System.out.println("This is Son1's method");
}
public void method(Son2 son){
System.out.println("This is Son2's method");
}
}
public class Test {
public static void main(String[] args){
Father father = new Father();
Father s1 = new Son1();
Father s2 = new Son2();
Execute exe = new Execute();
exe.method(father);
exe.method(s1);
exe.method(s2);
}
}
运行结果如下:
This is Father's method
This is Father's method
This is Father's method
在这里,程序在编译的时候就已经确定使用method(Father father)方法了,不管我们在运行的时候传入的实际类型是什么,它永远都只会执行method(Father father)这个方法。也就是说,java的重载是静态绑定的。
instanceof操作符与转型
有时候,我们希望在使用重载的时候,程序能够根据传入参数的实际类型动态地调用相应的方法,也就是说,我们希望java的重载是动态的,而不是静态的。但是由于java的重载不是动态绑定,我们只能通过程序来人为的判断,我们一般会使用instanceof操作符来进行类型的判断。我们要对method(Father father)进行修改,在方法体中判断运行期间的实际类型,修改后的method(Father father)方法如下:
public void method(Father father){
if(father instanceof Son1){
method((Son1)father);
}else if(father instanceof Son2){
method((Son2)father);
}else if(father instanceof Father){
System.out.println("This is Father's method");
}
}
请注意,我们必须把判断是否是父类的条件(也就是判断是否为Father类的条件)放到最后,否则将一律会被判断为Father类,达不到我们动态判断的目的。修改代码后,程序就可以动态地根据参数的实际类型来调用相应的方法了。运行结果如下:
This is Father's method
This is Son1's method
This is Son2's method
但是这种实现方式有一个明显的缺点,它是伪动态的,仍然需要我们来通过程序来判断类型。假如Father有100个子类的话,还是这样来实现显然是不合适的。必须通过其他更好的方式实现才行,我们可以使用双分派方式来实现动态绑定。
用双分派实现动态绑定
首先,什么是双分派?
类A中的方法method1和method2的区别就是,method2是双分派。我们可以看一下java双分派的特点:首先要有一个访问类B,类B提供一个showA(A a) 方法,在方法中,调用类A的method1方法,然后类A的method2方法中调用类B的showA方法并将自己作为参数传给showA。双分派的核心就是这个this对象。说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改,代码如下:
class Father {
public void accept(Execute exe){
exe.method(this);
}
}
class Son1 extends Father{
public void accept(Execute exe){
exe.method(this);
}
}
class Son2 extends Father{
public void accept(Execute exe){
exe.method(this);
}
}
class Execute {
public void method(Father father){
System.out.println("This is Father's method");
}
public void method(Son1 son){
System.out.println("This is Son1's method");
}
public void method(Son2 son){
System.out.println("This is Son2's method");
}
}
public class Test {
public static void main(String[] args){
Father father = new Father();
Father s1 = new Son1();
Father s2 = new Son2();
Execute exe = new Execute();
father.accept(exe);
s1.accept(exe);
s2.accept(exe);
}
}
可以看到我们修改的地方,在Father,Son1,Son2中分别加入一个双分派的方法。调用的时候,原本是调用Execute的method方法,现在改为调用Father的accept方法。运行结果如下:
This is Father's method
This is Son1's method
This is Son2's method
运行结果符合我们的预期,实现了动态绑定。双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了,与使用instanceof操作符的效果是一样的(用instanceof操作符可以实现重载方法动态绑定的原因也是因为instanceof操作符是动态的)。但是与使用instanceof操作符实现动态绑定相比,双分派方式的可扩展性要好的多。


猜你喜欢
- 排序算法很多地方都会用到,近期又重新看了一遍算法,并自己简单地实现了一遍,特此记录下来,为以后复习留点材料。废话不多说,下面逐一看看经典的排
- 依赖添加<dependency> <groupId>org.springframework.boot&l
- 因为在framework中想添加这个功能,所以写了个appliction来实现一下获取正在运行的应用程序: 还是先看图吧: 这个app主要是
- 1. Handler使用引出现在作为客户,有这样一个需求,当打开Activity界面时,开始倒计时,倒计时结束后跳转新的界面(思维活跃的朋友
- 前言数字时间戳技术是数字签名技术一种变种的应用。是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08
- 本文实例为大家分享了android实现文件读写功能的具体代码,供大家参考,具体内容如下读取:public static String _ge
- 项目上线之后,如果日志打印的很模糊或者业务逻辑比较复杂,有时候无法定位具体的错误原因,因此可以通过IDEA远程代理进行Debug。线上的代码
- 本文示例实现了Android退出时关闭所有Activity的功能,分享给大家供大家参考之用。具体方法如下:一般来说,在Android退出时,
- 命令模式定义:将请求封装成对象,这可以让你使用不同的请求、队列、或者日志来参数化其他对象。何时使用命令模式?当需要将发出请求的对象和执行请求
- 前言又没什么好的思路,还是随便写一些,所以这次就来整点活。我们都知道Activity的跳转拥有默认的跳转动画,或者把这个默认的动画给取消,就
- 在做 Android App 的时候,为了给用户省流量,为了不激起用户的愤怒,为了更好的用户体验,是需要根据用户当前网络情况来做一些调整的,
- 一、技术介绍线上演示地址:http://chat.breez.work实时通信(Instant Messaging,简称IM)是一个实时通信
- Spring-Data-Redis项目(简称SDR)对Redis的Key-Value数据存储操作提供了更高层次的抽象,类似于Spring F
- 在许多APP中,有的搜索框是一直固定的,有的呢,附加了很多的效果,就比如京东好吧,谁让京东那么厉害呢,不说了,开始 * !原理:就是自定义sc
- 前言事情的起因是微服务A通过feign调用微服务B的某个接口,报了形如下的异常feign.FeignException$NotFound:
- 代码很简单,这里就不多废话了。package cc.c;import android.app.Activity;import android
- IntelliJ IDEA安装好以后,按说我们就要双击进行启动了,但在启动之前,我得给大家说一下IntelliJ IDEA安装以后的安装目录
- Java针对多线程下的数值安全计数器设计了一些类,这些类叫做原子类,其中一部分如下:java.util.concurrent.atomic.
- public class TimeUtil { public static final int SECO
- 死锁问题死锁定义多线程编程中,因为抢占资源造成了线程无限等待的情况,此情况称为死锁。死锁举例注意:线程和锁的关系是:一个线程可以拥有多把锁,