Java中的静态绑定和动态绑定

一个Java程序的执行要通过编译和执行(解释)这两个步骤,同时Java又是面向对象的编程语言。当子类和父类存在同一个方法,子类重写了父类的方法,程序在运行时调用方法是调用父类的方法仍是子类的重写方法呢,这应该是咱们在初学Java时遇到的问题。这里首先咱们将肯定这种调用何种方法实现或者变量的操做叫作绑定。java

在Java中存在两种绑定方式,一种为静态绑定,又称做早期绑定。另外一种就是动态绑定,亦称为后期绑定。web

区别对比

  • 静态绑定发生在编译时期,动态绑定发生在运行时编程

  • 使用private或static或final修饰的变量或者方法,使用静态绑定。而虚方法(能够被子类重写的方法)则会根据运行时的对象进行动态绑定。安全

  • 静态绑定使用类信息来完成,而动态绑定则须要使用对象信息来完成。ruby

  • 重载(Overload)的方法使用静态绑定完成,而重写(Override)的方法则使用动态绑定完成。框架

重载方法的示例

这里展现一个重载方法的示例。编程语言

public class TestMain {  public static void main(String[] args) {      String str = new String();      Caller caller = new Caller();      caller.call(str);  }  static class Caller {      public void call(Object obj) {          System.out.println("an Object instance in Caller");      }            public void call(String str) {          System.out.println("a String instance in in Caller");      }  }}

执行的结果为ide

22:19 $ java TestMaina String instance in in Caller

在上面的代码中,call方法存在两个重载的实现,一个是接收Object类型的对象做为参数,另外一个则是接收String类型的对象做为参数。str是一个String对象,全部接收String类型参数的call方法会被调用。而这里的绑定就是在编译时期根据参数类型进行的静态绑定。优化

验证

光看表象没法证实是进行了静态绑定,使用javap发编译一下便可验证。spa

22:19 $ javap -c TestMainCompiled from "TestMain.java"public class TestMain {  public TestMain();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."<init>":()V       4: return  public static void main(java.lang.String[]);    Code:       0: new           #2                  // class java/lang/String       3: dup       4: invokespecial #3                  // Method java/lang/String."<init>":()V       7: astore_1       8: new           #4                  // class TestMain$Caller      11: dup      12: invokespecial #5                  // Method TestMain$Caller."<init>":()V      15: astore_2      16: aload_2      17: aload_1      18: invokevirtual #6                  // Method TestMain$Caller.call:(Ljava/lang/String;)V      21: return}

看到了这一行18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V确实是发生了静态绑定,肯定了调用了接收String对象做为参数的caller方法。

重写方法的示例

public class TestMain {  public static void main(String[] args) {      String str = new String();      Caller caller = new SubCaller();      caller.call(str);  }    static class Caller {      public void call(String str) {          System.out.println("a String instance in Caller");      }  }    static class SubCaller extends Caller {      @Override      public void call(String str) {          System.out.println("a String instance in SubCaller");      }  }}

执行的结果为

22:27 $ java TestMaina String instance in SubCaller

上面的代码,Caller中有一个call方法的实现,SubCaller继承Caller,而且重写了call方法的实现。咱们声明了一个Caller类型的变量callerSub,可是这个变量指向的时一个SubCaller的对象。根据结果能够看出,其调用了SubCaller的call方法实现,而非Caller的call方法。这一结果的产生的缘由是由于在运行时发生了动态绑定,在绑定过程当中须要肯定调用哪一个版本的call方法实现。

验证

使用javap不能直接验证动态绑定,而后若是证实没有进行静态绑定,那么就说明进行了动态绑定。

22:27 $ javap -c TestMainCompiled from "TestMain.java"public class TestMain {  public TestMain();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."<init>":()V       4: return  public static void main(java.lang.String[]);    Code:       0: new           #2                  // class java/lang/String       3: dup       4: invokespecial #3                  // Method java/lang/String."<init>":()V       7: astore_1       8: new           #4                  // class TestMain$SubCaller      11: dup      12: invokespecial #5                  // Method TestMain$SubCaller."<init>":()V      15: astore_2      16: aload_2      17: aload_1      18: invokevirtual #6                  // Method TestMain$Caller.call:(Ljava/lang/String;)V      21: return}

正如上面的结果,18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V这里是 TestMain$Caller.call 而非 TestMain$SubCaller.call ,由于编译期没法肯定调用子类仍是父类的实现,因此只能丢给运行时的动态绑定来处理。

当重载赶上重写

下面的例子有点变态哈,Caller类中存在call方法的两种重载,更复杂的是SubCaller集成Caller而且重写了这两个方法。其实这种状况是上面两种状况的复合状况。

下面的代码首先会发生静态绑定,肯定调用参数为String对象的call方法,而后在运行时进行动态绑定肯定执行子类仍是父类的call实现。

public class TestMain {  public static void main(String[] args) {      String str = new String();      Caller callerSub = new SubCaller();      callerSub.call(str);  }    static class Caller {      public void call(Object obj) {          System.out.println("an Object instance in Caller");      }            public void call(String str) {          System.out.println("a String instance in in Caller");      }  }    static class SubCaller extends Caller {      @Override      public void call(Object obj) {          System.out.println("an Object instance in SubCaller");      }            @Override      public void call(String str) {          System.out.println("a String instance in in SubCaller");      }  }}

执行结果为

22:30 $ java TestMaina String instance in in SubCaller

验证

因为上面已经介绍,这里只贴一下反编译结果啦

22:30 $ javap -c TestMainCompiled from "TestMain.java"public class TestMain {  public TestMain();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."<init>":()V       4: return  public static void main(java.lang.String[]);    Code:       0: new           #2                  // class java/lang/String       3: dup       4: invokespecial #3                  // Method java/lang/String."<init>":()V       7: astore_1       8: new           #4                  // class TestMain$SubCaller      11: dup      12: invokespecial #5                  // Method TestMain$SubCaller."<init>":()V      15: astore_2      16: aload_2      17: aload_1      18: invokevirtual #6                  // Method TestMain$Caller.call:(Ljava/lang/String;)V      21: return}

好奇问题

非动态绑定不可么?

其实理论上,某些方法的绑定也能够由静态绑定实现。好比

public static void main(String[] args) {      String str = new String();      final Caller callerSub = new SubCaller();      callerSub.call(str);}

好比这里callerSub持有subCaller的对象而且callerSub变量为final,当即执行了call方法,编译器理论上经过足够的分析代码,是能够知道应该调用SubCaller的call方法。

可是为何没有进行静态绑定呢?假设咱们的Caller继承自某一个框架的BaseCaller类,其实现了call方法,而BaseCaller继承自SuperCaller。SuperCaller中对call方法也进行了实现。

假设某框架1.0中的BaseCaller和SuperCaller

static class SuperCaller {  public void call(Object obj) {      System.out.println("an Object instance in SuperCaller");  }}  static class BaseCaller extends SuperCaller {  public void call(Object obj) {      System.out.println("an Object instance in BaseCaller");  }}

而咱们使用框架1.0进行了这样的实现。Caller继承自BaseCaller,而且调用了super.call方法。

public class TestMain {  public static void main(String[] args) {      Object obj = new Object();      SuperCaller callerSub = new SubCaller();      callerSub.call(obj);  }    static class Caller extends BaseCaller{      public void call(Object obj) {          System.out.println("an Object instance in Caller");          super.call(obj);      }            public void call(String str) {          System.out.println("a String instance in in Caller");      }  }    static class SubCaller extends Caller {      @Override      public void call(Object obj) {          System.out.println("an Object instance in SubCaller");      }            @Override      public void call(String str) {          System.out.println("a String instance in in SubCaller");      }  }}

而后咱们基于这个框架的1.0版编译出来了class文件,假设静态绑定能够肯定上面Caller的super.call为BaseCaller.call实现。

而后咱们再次假设这个框架1.1版本中SuperCaller和BaseCaller都改为了抽象方法不进行实现call方法,那么上面的假设能够静态绑定的call实如今1.1版本就会出现问题。

因此,有些实际能够静态绑定的,考虑到安全和一致性,就索性都进行了动态绑定。

获得的优化启示?

因为动态绑定须要在运行时肯定执行哪一个版本的方法实现或者变量,比起静态绑定起来要耗时。

因此 在不影响总体设计,咱们能够考虑将方法或者变量使用private,static或者final进行修饰。

相关文章
相关标签/搜索