干货篇: 内力提升 之 理解分派 - 访问者模式的精髓

先看一个“反直觉” 的代码示例

看下面这个例子

public class Fruit {

   public void printSelf(){

    }

}

public class Apple extends Fruit {

}

public class Pear extends Fruit {

}



public class NamePrinter {

  //参数是Fruit,打印Fruit
  public void print(Fruit fruit){
    System.out.println("print fruit");
  }

  //参数是Apple,打印Apple
  public void print(Apple apple){
    System.out.println("print apple");
  }

  //参数是Pear,打印Pear
  public void print(Pear pear){
    System.out.println("print pear");
  }

}



  @Test
  public void test1(){

    Fruit fruit = new Fruit();
    var apple = new Apple();
    var pear = new Pear();
    var namePrinter = new NamePrinter();

    namePrinter.print(fruit); 
    namePrinter.print(apple);
    namePrinter.print(pear);

  }

很简单的例子, 相信大家都不会出错, 输出

print fruit
print apple
print pear

让我们稍微修改一下代码,只改test方法里面变量的申明,其余不变

  @Test
  public void test1(){

    Fruit fruit = new Fruit();//var 修改为 Fruit
    Fruit apple = new Apple();//var 修改为 Fruit
    Fruit pear = new Pear();//var 修改为 Fruit

    NamePrinter namePrinter = new NamePrinter();

    namePrinter.print(fruit);
    namePrinter.print(apple);
    namePrinter.print(pear);

  }

想想, 输出是啥?

👇

print fruit

print fruit

print fruit

惊不惊喜?为什么仅修改变量的声明类型,方法调用行为就完全不同?这背后涉及 Java 的分派机制

分派

定义: 分派是指 JVM 在调用方法时,根据对象或参数的类型选择具体实现的过程。Java 的分派机制分为两个维度:时机(编译时 / 运行时)和维度(一维 / 多维)。

精简一点: 分派:类型决定方法执行

  • 静态分派:发生在编译阶段,依据变量的声明类型(静态类型)。
  • 动态分派:发生在运行阶段,依据对象的实际类型(动态类型)。

静态分派

    NamePrinter的三个print方法, 具有相同的名称,但是参数类型不一样,属于方法重载。

    public void print(Fruit person){
        System.out.println("print fruit");
      }
    
    public void print(Apple apple){
        System.out.println("print apple");
      }
    
    public void print(Pear pear){
        System.out.println("print pear");
      }

    在编译时,编译器只知道apple, pear 是Fruit类型,因此运行时调用重载方法的时候, 只能调用参数为申明Fruit类型的方法。 

        Fruit fruit = new Fruit();
        Fruit apple = new Apple();
        Fruit pear = new Pear();
    
        NamePrinter namePrinter = new NamePrinter();
    
        namePrinter.print(fruit);
        namePrinter.print(apple);
        namePrinter.print(pear);
    public void print(Fruit fruit){
        System.out.println("print fruit");
      }

    静态分派决定调用哪个重载方法。

    动态分派

    怎么修改?

    第一种方法是使用var 来申明变量, 这种方式其实是利用java 语法糖, 生成出来的.class文件其实是在编译时就修改成了申明的类型和new 后面对应的类型一致,实际上还是编译时来解决问题 

    第二种方式是通过运行时来动态根据对象类型来选择调用方法。

    最简单直接的,用instanceof 来解决

    public class NamePrinter {
    
    
      public void print(Fruit fruit) {
        if(fruit instanceof Apple){
              System.out.println("print apple");
        }else if(fruit instanceof Pear){
              System.out.println("print pear");
        }else{
              System.out.println("print fruit");
        }
      }
    
    }

    进阶一点的, 每个子类去重写print方法.

    public class Fruit {
      public void print() {
        System.out.println("print fruit");
      }
    }
    
    public class Apple extends Fruit {
    
      //字类重写print方法
      public void print() {
        System.out.println("print apple");
      }
    
    }
    
    public class Pear extends Fruit {
    
      //字类重写print方法
      public void print() {
        System.out.println("print pear");
      }
    }
    
    
    public class NamePrinter {
    
      public void print(Fruit fruit) {
            fruit.print();
      }
    }
    
    public class TestFruit {
    
      @Test
      public void test1() {
    
        Fruit fruit = new Fruit();
        Fruit apple = new Apple();
        Fruit pear = new Pear();
    
        NamePrinter namePrinter = new NamePrinter();
    
        namePrinter.print(fruit);
        namePrinter.print(apple);
        namePrinter.print(pear);
      }
    }
    

    输出

    print fruit

    print apple

    print pear

    单分派 vs 双分派:分派机制的维度升级

    单分派

    上面第二种优化方案,其实就是单分派。 NamePrinter只有一个类, NamePrinter.print(Fruit fruit)根据Fruit的参数, 来决定调用Fruit的哪个子类的print方法。 这种单一维度的类型信息来决定调用哪个方法,属于单分派

    双分派

    但在某些复杂场景中,我们需要根据两个维度的类型信息来决定执行哪个方法,这就是双分派 。

    比如NamePrinter也有多个字类呢? 怎么在运行时,根据NamePrinterFruit的对象类型,来动态的知道调用哪个NamePrinter的print方法,以及调用哪个Fruit字类的print方法呢?这就是双分派问题。

    怎么解决双分派问题? JAVA 不支持双分派, 访问者模式是唯一的解决方法。

    总结

    Java 方法调用的 “反直觉” 表现源于分派机制

    • 分派机制分为两个维度:时机维度
    • 按时机来说, 分派有静态分派(编译时) ,  动态分派(运行时).
      • 静态分派-重载
      • 动态分派-重写
    • 按维度来说, 有单分派, 双分派
      • 单分派:仅一维类型判断(声明或实际类型)。
      • 双分派:需二维类型协作(如元素类型 + 操作类型),Java 需通过访问者模式实现,通过两次动态调用解耦多维度逻辑。

    核心结论:分派维度决定代码灵活性,双分派是复杂场景下的解耦关键,访问者模式是 Java 的标准解法。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

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

    余额充值