Java的动态分派和静态分派的实现

2020-03-09 16:01:30王振洲

main 方法的执行结果:

human
human
human

虽然 StaticDispatch 为每种 Human 的子类都重载了一个 say 方法,但是由于重载采用的是静态分派,是根据对象的静态类型做方法匹配的。所以结果全都匹配到了 public void say(Human human) 方法。main 方法编译之后的字节码:

public static main([Ljava/lang/String;)V
 NEW method_invoke/StaticDispatch$Man
 DUP
 INVOKESPECIAL method_invoke/StaticDispatch$Man.<init> ()V
 ASTORE 1
 NEW method_invoke/StaticDispatch$Woman
 DUP
 INVOKESPECIAL method_invoke/StaticDispatch$Woman.<init> ()V
 ASTORE 2
 NEW method_invoke/StaticDispatch$Child
 DUP
 INVOKESPECIAL method_invoke/StaticDispatch$Child.<init> ()V
 ASTORE 3
 NEW method_invoke/StaticDispatch
 DUP
 INVOKESPECIAL method_invoke/StaticDispatch.<init> ()V
 ASTORE 4
 // 下面为调用 say
 ALOAD 4
 ALOAD 1
 INVOKEVIRTUAL method_invoke/StaticDispatch.say (Lmethod_invoke/StaticDispatch$Human;)V
 ALOAD 4
 ALOAD 2
 INVOKEVIRTUAL method_invoke/StaticDispatch.say (Lmethod_invoke/StaticDispatch$Human;)V
 ALOAD 4
 ALOAD 3
 INVOKEVIRTUAL method_invoke/StaticDispatch.say (Lmethod_invoke/StaticDispatch$Human;)V
 RETURN

从字节码也能看到,编译器确实是按照静态分派选择了匹配静态类型的 StaticDispatch.say(LStaticDispatch$Human;)V 方法,而没有按照变量的实际类型去匹配重载的方法。

public class Overload {
 public static void out(char a) { System.out.println("char " + a); }
 public static void out(int a) {System.out.println("int " + a);}
 public static void out(long a) { System.out.println("long " + a); }
 public static void out(float a) { System.out.println("float " + a); }
 public static void out(double a) { System.out.println("double " + a); }
 public static void out(Integer a) { System.out.println("integer"); }
 public static void out(Character a) { System.out.println("character"); }
 public static void out(Serializable a) { System.out.println("serializable " + a); }
 public static void out(Comparable a) { System.out.println("comparable " + a); }
 public static void out(Object a) { System.out.println("object " + a); }
 public static void out(char... a) { System.out.println("char ... " + Arrays.toString(a)); }

 public static void main(String[] args) {
  out('c');
 }
}

这段代码也是一个静态分派的例子,编译器会选择参数类型做合适的函数去调用。可以注释掉所有 out 函数,留下 out(Serializable a),你会发现程序也能成功编译和运行。如果留下Serializeable 和 Comparable 编译则会失败,提示对 out 的引用不明确。

动态分派

根据变量的「实际类型」匹配调用方法的过程称为动态分派。发生的场景为方法重写。当调用一个可能被子类重写或继承的方法时,就会触发动态分派。

public class DynamicDispatch {

 static class Human {
  public void say() {
   System.out.println("human");
  }
 }

 static class Man extends Human {
  @Override
  public void say() {
   System.out.println("man");
  }
 }

 static class Woman extends Human {
  @Override
  public void say() {
   System.out.println("woman");
  }
 }
}