文档章节

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

断崖逐梦
 断崖逐梦
发布于 2015/01/04 23:53
字数 1918
阅读 54
收藏 1

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

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

区别对比

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

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

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

  • 重载(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");      }  }}

执行的结果为

22:19 $ java TestMaina String instance in in Caller

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

验证

光看表象无法证明是进行了静态绑定,使用javap发编译一下即可验证。

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进行修饰。

 

本文转载自:http://droidyue.com/blog/2014/12/28/static-biding-and-dynamic-binding-in-java/

断崖逐梦
粉丝 14
博文 4
码字总数 1556
作品 0
长沙
程序员
私信 提问
加载中

评论(0)

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

一个Java程序的执行要经过编译和执行(解释)这两个步骤,同时Java又是面向对象的编程语言。当子类和父类存在同一个方法,子类重写了父类的方法,程序在运行时调用方法是调用父类的方法还是子...

enosh
2015/01/07
36
0
java方法重载重写原理学习的简单总结

概述 根据查看过的 深入理解JVM 和 郑雨迪的教程,对java中方法重载的原理进行一个大致总结 具体总结 在JAVA中,多态主要体现为方法的重载和重写。 方法重载:同一个类中,方法名相同,参数列...

娑婆丶
2019/02/18
54
0
04-《深度拆解JVM》之 JVM是如何执行方法调用的?(上)

一、问题引入 前不久在写代码的时候,郑雨迪老师不小心踩到一个可变长参数的坑。它就是由于可变长参数方法的重载造成的。(注:官方文档建议避免重载可变长参数方法,见 [1] 的最后一段。) ...

飞鱼说编程
2018/09/18
22
0
05-《深度拆解JVM》之JVM是如何执行方法调用的?(下)

一、问题引入 我们知道,设计模式大量使用了虚方法来实现多态。但是虚方法的性能效率并不高,所以作者就想在此基础上写篇文章,评估每一种设计模式因为虚方法调用而造成的性能开销,并且在文...

飞鱼说编程
2018/09/25
36
0
深入理解 Java 中的 override 和 overload

override 和 overload,分别翻译为覆写和重载,是 Java 多态(Polymorphism)的两种代表类型。下面详细分析一下 override 和 overload 的使用方式。 1. override override 出现在继承关系中,...

落英坠露
2019/07/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

 企业信息平台的快速搭建,框架如何选?

Web端开发框架如何选 目前,大部分的企业信息集成系统都在web端运行,而搭建框架的选择对一个企业的发展至关重要,不过其最终目的都是要符合企业发展逻辑,助力企业战略的实施。 而在框架的选...

我想造火箭
15分钟前
25
0
安装mysql 实操截图

前言: CentOS 7 版本将MySQL数据库软件从默认的程序列表中移除,用MariaDB代替了,MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可。开发这个分支的原因之...

冥焱
19分钟前
57
0
FecMall 多商户分销系统 - 价格公式计算

FecMall Fecbdc 分销价格公式计算 本章详解讲述分销平台的各个价格,以及相应的设置,本章节非常重要,贯穿分销系统的整个流程,请仔细阅读 官网: http://www.fecmall.com/ 业务逻辑设计 系...

FecShop
20分钟前
33
0
Java Web 学习笔记(7)

文件下载 package com.janeroad.servlet;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.......

JaneRoad
24分钟前
41
0
如何在JavaScript中更改span元素的文本

如果我有跨度,请说: <span id="myspan"> hereismytext </span> 如何使用JavaScript将“ hereismytext”更改为“ newtext”? #1楼 对于现代浏览器,您应该使用: document.getElementByI......

技术盛宴
26分钟前
46
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部