java方法调用之单分派与多分派(二)

上篇博文java方法调用之重载、重写的调用原理(一) 讨论了重写与重载的实现原理,这篇博文讨论下单分派与多分派。

单分派、多分派

方法的接收者和方法的参数统称为方法的宗量。 根据分派基于宗量多少(接收者是一个宗量,参数是一个宗量),可以将分派分为单分派和多分派。单分派是指根据一个宗量就可以知道调用目标(即应该调用哪个方法),多分派需要根据多个宗量才能确定调用目标。
请看示例:

/**
 * Created by fan on 2016/3/29.
 */
public class Dispatcher {
    static class QQ {}
    static class _360 {}

    public static class Father {
        public void hardChoice(QQ arg) {
            System.out.println("father choose QQ");
        }

        public void hardChoice(_360 arg) {
            System.out.println("father choose _360");
        }
    }

    public static class Son extends Father {
        @Override
        public void hardChoice(QQ arg) {
            System.out.println("son choose QQ");
        }

        @Override
        public void hardChoice(_360 arg) {
            System.out.println("son choose 360");
        }
    }

    public static void main(String[] args) {
        Father father = new Father();
        Father son = new Son();
        father.hardChoice(new _360());
        son.hardChoice(new QQ());
    }
}

执行结果如下所示:
这里写图片描述
字节码指令如下所示:

public static void main(java.lang.String[]);
  Code:
   Stack=3, Locals=3, Args_size=1
   0:   new     #2; //class Dispatcher$Father
   3:   dup
   4:   invokespecial   #3; //Method Dispatcher$Father."":()V
   7:   astore_1
   8:   new     #4; //class Dispatcher$Son
   11:  dup
   12:  invokespecial   #5; //Method Dispatcher$Son."":()V
   15:  astore_2
   16:  aload_1
   17:  new     #6; //class Dispatcher$_360
   20:  dup
   21:  invokespecial   #7; //Method Dispatcher$_360."":()V
   24:  invokevirtual   #8; //Method Dispatcher$Father.hardChoice:(LDispatcher$_360;)V
   27:  aload_2
   28:  new     #9; //class Dispatcher$QQ
   31:  dup
   32:  invokespecial   #10; //Method Dispatcher$QQ."":()V
   35:  invokevirtual   #11; //Method Dispatcher$Father.hardChoice:(LDispatcher$QQ;)V
   38:  return

从上面的字节码指令中可以看到,两次方法调用

        father.hardChoice(new _360());
        son.hardChoice(new QQ());

对应的字节码指令都是一样的,只是参数不同而已:

   24:  invokevirtual   #8; //Method Dispatcher$Father.hardChoice:(LDispatcher$_360;)V
   35:  invokevirtual   #11; //Method Dispatcher$Father.hardChoice:(LDispatcher$QQ;)V

由此可见,在class文件中都是调用Father的hardChoice()方法。

解析

在java源代码进行编译的过程中,发生了这么个事情。
首先确定方法的接收者,发现两个对象变量的静态类型都是Father类型的,因此在class文件中写的Father类中方法的符号引用。
再者,对于方法参数,一个是_360对象,一个是QQ对象,按照静态类型匹配的原则,自然找到各自的方法。
上面的两步都是在编译器中做出的,属于静态分派,在选择目标方法时根据了两个宗量,是多分派的。因此,静态分派属于多分派类型。
当java执行时,当执行到son.hardChoice(new QQ()); 时,发现son的实际类型是Son,因此会调用Son类中的方法。在执行father.hardChoice(new _360()); 时也有这个过程,只不过father的实际类型就是Father而已。发现,在目标选择时只依据了一个宗量,是单分派的。因此,动态分派属于单分派类型。

结论

到目前为止,java语言是一个静态多分派,动态单分派的语言。下篇博文,将讨论讨论动态分派(即多态)的实现原理 java方法调用之动态调用多态(重写override)的实现原理——方法表(三) 。

参考资料

  • 周志明 《深入理解JAVA虚拟机》

你可能感兴趣的:(java)