Java实现双分派

最近在看SparkSQL的实现,推荐一本书《SparkSQL 内核剖析》,讲的很好。
关于SparkSQL如何把一条String类型的SQL识别解析,在Spark 2.0版本,使用的是ANTLR4来进行语法和词法分析,构造出语法分析树,然后通过SparkSQL的astBuilder这个对象去访问这棵树,在访问的过程中讲语法分析树逐步转换为unresolved logical plan。在这个访问语法树的过程中,SparkSQL采用的是访问者模式。

在追踪源码的过程中,总是在各种visit(this)和accept(Visitor)方法中跳来跳去,由于之前并未了解过访问者设计模式,遂场面一度十分混乱。这也正是写这篇文章的缘由。(是不是感觉逻辑清晰,没错,再夸我我会骄傲的)

一、Java动态绑定与双分派

从一个看过访问者模式的学习者的角度,为了更好的理解访问者设计模式,最好先要了解双分派是什么。

参考一篇让我茅塞顿开的文章:https://www.cnblogs.com/liaokailin/p/3804437.html

1.1动态绑定

动态绑定是指程序执行期间(而不是在编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法 。

如下代码:

package com.dp.zhb;

public class DynamicBound {
    public static void main(String[] args) {
        Person person = new Man() ;
        person.say() ;
    }
}

class Person{
    public void say(){} ;
}

class Man extends Person{
    public void say(){
        System.out.println("Hey Man");
    }
}

运行结果:

调用的是Person对象中的say方法 但是实际执行的是Man中的方法,这就是动态绑定。

1.2静态绑定

静态绑定就是指在编译期就已经确定执行哪一个方法。方法的重载(方法名相同而参数不同)就是静态绑定的,重载时,执行哪一个方法在编译期就已经确定下来。

package com.dp.zhb;

public class StaticBound {
    public static void main(String[] args) {
        OutputName out = new OutputName() ;
        Person p = new Person() ;
        Person man = new Man() ;
        Person woman = new Woman() ;
        out.print(p) ;
        out.print(man) ;
        out.print(woman) ;
    }
}


class Person{
}

class Man extends Person{

}
class Woman extends Person{

}

class OutputName{
    void print(Person p){
        System.out.println("person");
    }
    void print(Man m){
        System.out.println("man");
    }
    void print(Woman w){
        System.out.println("woman");
    }
}

上面这个程序输出结果:

不管在运行的时候传入的实际类型是什么,它永远都只会执行 void print(Person p)这个方法,即 : 重载是静态绑定的。

如果希望使用重载的时候,程序能够根据传入参数的实际类型动态地调用相应的方法,只能通过instanceof操作符进行类型的判断,然后再进行调用。虽然可以解决问题,但是如果子类数目很多,那么就要写很过个if else来判断类型,显然不是这种解决方案不是很合适。上述代码如下:

package com.dp.zhb;

public class StaticBound {
    public static void main(String[] args) {
        OutputName out = new OutputName() ;
        Person p = new Person() ;
        Person man = new Man() ;
        Person woman = new Woman() ;
        out.print(p) ;
        out.print(man) ;
        out.print(woman) ;
    }
}


class Person{
}

class Man extends Person{

}
class Woman extends Person{

}

class OutputName{
    void print(Person p){
        if(p instanceof Man) print((Man)p);
        else if (p instanceof Woman) print((Woman)p);
        else  System.out.println("person");
    }
    void print(Man m){
        System.out.println("man");
    }
    void print(Woman w){
        System.out.println("woman");
    }
}

结果:

1.3 双分派

分派( dispatch)是指运行环境按照对象的实际类型为其绑定对应方法体的过程。

double dispatch(双分派)在选择一个方法的时候,不仅仅要根据消息接收者(receiver) 的运行时型别(Run time type),还要根据参数的运行时型别(Run time type)。这里的消息接收者其实就是方法的调用者。具体来讲就是,对于消息表达式a.m(b),双分派能够按照a和b的实际类型为其绑定对应方法体。

来看一个双分派的例子:

package com.dp.zhb;

class Father {
    public void accept(Execute exe){
        exe.method(this);
    }
}
class Son1 extends Father{
    @Override
    public void accept(Execute exe){
        exe.method(this);
    }
}
class Son2 extends Father{
    @Override
    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);
    }
}

运行结果:

通俗的解释一下,就是重载是静态绑定,重写是动态绑定,双分派把重写放在重载之前,以实现在运行时动态判断执行那个子类的方法。上面的例子中,首先依据重写(Java的多态)找到对应的accept方法,然后accept方法中调用method方法, 并把当前类的this传入method,传入this这步就属于是静态绑定,在编译器就确定好的。比如在Father类中:

class Father {
    public void accept(Execute exe){
        exe.method(this);
    }
}

method(this)就对应了Execute类中的

public void method(Father father){
        System.out.println("This is Father's method");
    }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
内容简介: 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 本课程内容定位学习设计原则,学习设计模式的基础。在实际开发过程中,并不是一定要求所有代码都遵循设计原则,我们要考虑人力、时间、成本、质量,不是刻意追求完美,要在适当的场景遵循设计原则,体现的是一种平衡取舍,帮助我们设计出更加优雅的代码结构。本章将详细介绍开闭原则(OCP)、依赖倒置原则(DIP)、单一职责原则(SRP)、接口隔离原则(ISP)、迪米特法则(LoD)、里氏替换原则(LSP)、合成复用原则(CARP)的具体内容。 为什么需要学习这门课程? 你在日常的开发中,会不会也遇到过同样的问题。系统出现问题,不知道问题究竟出在什么位置;当遇到产品需求,总是对代码缝缝补补,不能很快的去解决。而且平时工作中,总喜欢把代码堆在一起,出现问题时,不知道如何下手,工作效率很低,而且自己的能力也得不到提升。而这些都源于一个问题,那就是软件设计没做好。这门课能帮助你很好的认识设计模式,让你的能力得到提升。课程大纲: 为了让大家快速系统了解设计模式知识全貌,我为您总结了思维导图,帮您梳理学习重点,建议收藏!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叹了口丶气

觉得有收获就支持一下吧~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值