精华内容
下载资源
问答
  • JAVA动态绑定机制

    2016-05-10 11:09:43
    一个方法可能在几个子类中实现,JAVA虚拟机在运行时动态绑定方法的实现,由变量引用的对象的实际类决定。 动态绑定的工作机制如下:假设对象o是类c1,c2.......cn的实例,其中c1是c2的子类,c2是c3的子

    子类方法对父类方法的覆盖以及一般方法的重载都可以看作是多态。

    动态绑定是一种机制。引用变量的声明类型决定了编译时哪个方法匹配;编译时,编译器根据参数类型、参数个数和参数顺序匹配方法。一个方法可能在几个子类中实现,JAVA虚拟机在运行时动态绑定方法的实现,由变量引用的对象的实际类决定。

    动态绑定的工作机制如下:假设对象o是类c1,c2.......cn的实例,其中c1是c2的子类,c2是c3的子类......cn-1是cn的子类,也就是说cn是最一般的类,c1是最特殊的类。如果对象o调用一个方法p,JAVA虚拟机依此在类c1,c2.......cn中找方法P的第一个实现。

    package com.mypackage.extend;
    public class Animal {
       String type;
       String name;
       int age;
       int weight;
       void eat()
       {
      System.out.println("动物爱吃饭");
       }
       
       void breath()
       {
      System.out.println("动物呼吸");
       }
       
       void sleep(){
      System.out.println("动物睡觉");
       }
    }

    class Tiger extends Animal
    {
    String tigerType;
    String from;
    @Override
    void breath() {
    // TODO Auto-generated method stub
    //super.breath();
    System.out.println("老虎用肺呼吸");
    }

    void tigerRun()
    {
    System.out.println("老虎在奔跑");
    }
    }


    不能通过数组来直接访问Tiger类自己的方法,必须要进行类型强制转换。因为动态绑定只对子类覆盖父类的方法有效,其他一般方法调用由声明的类型决定。使用A instanceof  B用来判断A是否是B的一个实例,防止ClassCastException出现。


    public class TestAnimal {
    /**
    * @param args
    */
    public static void main(String[] args) {
    // TODO Auto-generated method stub
         Animal[] animal = new Animal[2];
         animal[0] = new Animal();
         animal[1] = new Tiger();
         
         animal[0].breath();
         animal[1].breath();
        
         if(animal[1] instanceof Tiger)
        ((Tiger)animal[1]).tigerRun();
    }
    }

    展开全文
  • Java动态绑定机制详解

    2016-10-15 11:01:23
    java动态绑定机制详解  动态绑定意思是目前正在调用的方法正是最适用于要操作对象的那个方法。为了执行动态绑定,java使用一小段特殊的代码来替代绝对地址的调用,这样,根据这段代码的内容 ,每个对象都可以具有...
    java动态绑定机制详解  
    动态绑定意思是目前正在调用的方法正是最适用于要操作对象的那个方法。为了执行动态绑定,java使用一小段特殊的代码来替代绝对地址的调用,这样,根据这段代码的内容
    ,每个对象都可以具有不同的行为表现。当向一个对象发送消息时,该对象就能够知道这条消息应该做些什么。
    举个例子;
    首先创建一个父类Shape,方法有draw()、erase()、move()、getColor()和setColor(),子类Circle()、Square()和Triangle()分别继承父类的draw()和erase()方法。编写一个方法:
    void doSomething(Shape shape) {
    Shape.erase();
    //...
    Shape.draw();
    }
    这个方法可以与任何Shape(即Circle、Square和Triangle)对话,因此它是独立于任何它要draw和erase的对象的具体类,如果程序中其他部分调用dosomething()方法:
    Circle circle = new Circle();
    Triangle triangle = new Triangle();
    Square square = new Square();
    doSomething(circle);
    doSomething( triangle);
    doSomething( square);
    对doSomething()的调用会自动地调用相应的类。如:
    Circle可以被doSomething()看做Shape,Shape.erase()并不是说“如果是Circle,请这么做;如果是Squre,请那么做”。如果编写了那种检查Shape所有实际可能的子类的代码,那么这段代码肯定杂乱不堪,则例要表达的仅仅是“你是一个Shape,我知道你可以erase你自己,那么去做吧,但是要注意细节的正确性。”
    这里是把子类作为参数传入到doSomething(),然后调用里面的方法。调用Circle的draw()和调用Squre的draw()方法所执行的代码是不同的,即所谓的多态就出现了。
    当Java编译器在编译doSomething()的代码时,并不能确切知道doSomething()要处理的确切的子类,所以通常会期望它的编译结果是调用父类Shape的erase()和draw()方法,而不是子类的方法。


    本文绝大部分参考《Java编程思想》。

    展开全文
  • Java动态绑定机制原理分析

    千次阅读 2016-05-27 18:38:49
    这篇文章我们就将揭露JVM方法调用的静态(static binding) 和动态绑定机制(auto binding) 。     ★ 静态绑定机制 Java代码  //被调用的类  package hr.test;  class Father{ 


    在Java方法调用的过程中,JVM是如何知道调用的是哪个类的方法源代码? 这里面到底有什么内幕呢? 这篇文章我们就将揭露JVM方法调用的静态(static binding) 动态绑定机制(auto binding) 

     

     

    ★ 静态绑定机制

    Java代码  收藏代码
    1. //被调用的类  
    2. package hr.test;  
    3. class Father{  
    4.       public static void f1(){  
    5.               System.out.println("Father— f1()");  
    6.       }  
    7. }  
    8. //调用静态方法  
    9. import hr.test.Father;  
    10. public class StaticCall{  
    11.        public static void main(){  
    12.             Father.f1(); //调用静态方法  
    13.        }  
    14. }  

         上面的源代码中执行方法调用的语句(Father.f1())被编译器编译成了一条指令:invokestatic #13。我们看看JVM是如何处理这条指令的

         (1) 指令中的#13指的是StaticCall类的常量池中第13个常量表的索引项(关于常量池详见《Class文件内容及常量池 》)。这个常量表(CONSTATN_Methodref_info ) 记录的是方法f1信息的符号引用(包括f1所在的类名,方法名和返回类型)。JVM会首先根据这个符号引用找到方法f1所在的类的全限定名: hr.test.Father。

         (2) 紧接着JVM会加载、链接和初始化Father类。

         (3) 然后在Father类所在的方法区中找到f1()方法的直接地址,并将这个直接地址记录到StaticCall类的常量池索引为13的常量表中。这个过程叫常量池解析 ,以后再次调用Father.f1()时,将直接找到f1方法的字节码。

         (4) 完成了StaticCall类常量池索引项13的常量表的解析之后,JVM就可以调用f1()方法,并开始解释执行f1()方法中的指令了。

     

         通过上面的过程,我们发现经过常量池解析之后,JVM就能够确定要调用的f1()方法具体在内存的什么位置上了。实际上,这个信息在编译阶段就已经在StaticCall类的常量池中记录了下来。这种在编译阶段就能够确定调用哪个方法的方式,我们叫做 静态绑定机制 

     

         除了被static 修饰的静态方法,所有被private 修饰的私有方法、被final 修饰的禁止子类覆盖的方法都会被编译成invokestatic指令。另外所有类的初始化方法<init>和<clinit>会被编译成invokespecial指令。JVM会采用静态绑定机制来顺利的调用这些方法。

     

     

     

    ★ 动态绑定机制

    Java代码  收藏代码
    1. package hr.test;  
    2. //被调用的父类  
    3. class Father{  
    4.     public void f1(){  
    5.         System.out.println("father-f1()");  
    6.     }  
    7.         public void f1(int i){  
    8.                 System.out.println("father-f1()  para-int "+i);  
    9.         }  
    10. }  
    11. //被调用的子类  
    12. class Son extends Father{  
    13.     public void f1(){ //覆盖父类的方法  
    14.         System.out.println("Son-f1()");  
    15.     }  
    16.         public void f1(char c){  
    17.                 System.out.println("Son-s1() para-char "+c);  
    18.         }  
    19. }  
    20.   
    21. //调用方法  
    22. import hr.test.*;  
    23. public class AutoCall{  
    24.     public static void main(String[] args){  
    25.         Father father=new Son(); //多态  
    26.         father.f1(); //打印结果: Son-f1()  
    27.     }  
    28. }  

          上面的源代码中有三个重要的概念:多态(polymorphism) 方法覆盖 、方法重载 。打印的结果大家也都比较清楚,但是JVM是如何知道f.f1()调用的是子类Sun中方法而不是Father中的方法呢?在解释这个问题之前,我们首先简单的讲下JVM管理的一个非常重要的数据结构——方法表 

     

           在JVM加载类的同时,会在方法区中为这个类存放很多信息(详见《Java 虚拟机体系结构 》)。其中就有一个数据结构叫方法表。它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址 。下图是上面源代码中Father和Sun类在方法区中的方法表:

     

          上图中的方法表有两个特点:(1) 子类方法表中继承了父类的方法,比如Father extends Object。 (2) 相同的方法(相同的方法签名:方法名和参数列表)在所有类的方法表中的索引相同。比如Father方法表中的f1()和Son方法表中的f1()都位于各自方法表的第11项中。

     

          对于上面的源代码,编译器首先会把main方法编译成下面的字节码指令:

    多态调用的字节码指令代码  收藏代码
    1. 0  new hr.test.Son [13] //在堆中开辟一个Son对象的内存空间,并将对象引用压入操作数栈  
    2. 3  dup    
    3. 4  invokespecial #7 [15] // 调用初始化方法来初始化堆中的Son对象   
    4. 7  astore_1 //弹出操作数栈的Son对象引用压入局部变量1中  
    5. 8  aload_1 //取出局部变量1中的对象引用压入操作数栈  
    6. 9  invokevirtual #15 //调用f1()方法  
    7. 12  return  

           其中invokevirtual指令的详细调用过程是这样的:

           (1) invokevirtual指令中的#15指的是AutoCall类的常量池中第15个常量表的索引项。这个常量表(CONSTATN_Methodref_info ) 记录的是方法f1信息的符号引用(包括f1所在的类名,方法名和返回类型)。JVM会首先根据这个符号引用找到调用方法f1的类的全限定名: hr.test.Father。这是因为调用方法f1的类的对象father声明为Father类型。

           (2) 在Father类型的方法表中查找方法f1,如果找到,则将方法f1在方法表中的索引项11(如上图)记录到AutoCall类的常量池中第15个常量表中(常量池解析 )。这里有一点要注意:如果Father类型方法表中没有方法f1,那么即使Son类型中方法表有,编译的时候也通过不了。因为调用方法f1的类的对象father的声明为Father类型。

           (3) 在调用invokevirtual指令前有一个aload_1指令,它会将开始创建在堆中的Son对象的引用压入操作数栈。然后invokevirtual指令会根据这个Son对象的引用首先找到堆中的Son对象,然后进一步找到Son对象所属类型的方法表。过程如下图所示:

                        

          (4) 这是通过第(2)步中解析完成的#15常量表中的方法表的索引项11,可以定位到Son类型方法表中的方法f1(),然后通过直接地址找到该方法字节码所在的内存空间。

     

          很明显,根据对象(father)的声明类型(Father)还不能够确定调用方法f1的位置,必须根据father在堆中实际创建的对象类型Son来确定f1方法所在的位置。这种在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,我们叫做 动态绑定机制 

     

          上面的过程很清楚的反映出在方法覆盖的多态调用的情况下,JVM是如何定位到准确的方法的。但是下面的调用方法JVM是如何定位的呢?(仍然使用上面代码中的Father和Son类型)

    Java代码  收藏代码
    1. public class AutoCall{  
    2.        public static void main(String[] args){  
    3.              Father father=new Son();  
    4.              char c='a';  
    5.              father.f1(c); //打印结果:father-f1()  para-int 97  
    6.        }  
    7. }  

           问题是Fahter类型中并没有方法签名为f1(char)的方法呀。但打印结果显示JVM调用了Father类型中的f1(int)方法,并没有调用到Son类型中的f1(char)方法。

     

           根据上面详细阐述的调用过程,首先可以明确的是:JVM首先是根据对象father声明的类型Father来解析常量池的(也就是用Father方法表中的索引项来代替常量池中的符号引用)。如果Father中没有匹配到"合适" 的方法,就无法进行常量池解析,这在编译阶段就通过不了。

          那么什么叫"合适"的方法呢?当然,方法签名完全一样的方法自然是合适的。但是如果方法中的参数类型在声明的类型中并不能找到呢?比如上面的代码中调用father.f1(char),Father类型并没有f1(char)的方法签名。实际上,JVM会找到一种“凑合”的办法,就是通过 参数的自动转型 来找 到“合适”的 方法。比如char可以通过自动转型成int,那么Father类中就可以匹配到这个方法了 (关于Java的自动转型问题可以参见《【解惑】Java类型间的转型》)。但是还有一个问题,如果通过自动转型发现可以“凑合”出两个方法的话怎么办?比如下面的代码:

    Java代码  收藏代码
    1. class Father{  
    2.     public void f1(Object o){  
    3.         System.out.println("Object");  
    4.     }  
    5.     public void f1(double[] d){  
    6.         System.out.println("double[]");  
    7.     }  
    8.       
    9. }  
    10. public class Demo{  
    11.     public static void main(String[] args) {  
    12.         new Father().f1(null); //打印结果: double[]  
    13.     }  
    14. }  

            null可以引用于任何的引用类型,那么JVM如何确定“合适”的方法呢。一个很重要的标准就是:如果一个方法可以接受传递给另一个方法的任何参数,那么第一个方法就相对不合适。比如上面的代码: 任何传递给f1(double[])方法的参数都可以传递给f1(Object)方法,而反之却不行,那么f1(double[])方法就更合适。因此JVM就会调用这个更合适的方法。

     

     

    ★ 总结

     

          (1) 所有私有方法、静态方法、构造器及初始化方法<clinit>都是采用静态绑定机制。在编译器阶段就已经指明了调用方法在常量池中的符号引用,JVM运行的时候只需要进行一次常量池解析即可。


          (2) 类对象方法的调用必须在运行过程中采用动态绑定机制。

               首先,根据对象的声明类型(对象引用的类型)找到“合适”的方法。具体步骤如下:

               ① 如果能在声明类型中匹配到方法签名完全一样(参数类型一致)的方法,那么这个方法是最合适的。

               ② 在第①条不能满足的情况下,寻找可以“凑合”的方法。标准就是通过将参数类型进行自动转型之后再进行匹配。如果匹配到多个自动转型后的方法签名f(A)和f(B),则用下面的标准来确定合适的方法:传递给f(A)方法的参数都可以传递给f(B),则f(A)最合适。反之f(B)最合适 。

     

               ③ 如果仍然在声明类型中找不到“合适”的方法,则编译阶段就无法通过。

     

               然后,根据在堆中创建对象的实际类型找到对应的方法表,从中确定具体的方法在内存中的位置。

     

     

     

    ★ 覆写(override)

           一个实例方法可以覆写(override)在其超类中可访问到的具有相同签名的所有实例方法,从而使能了动态分派(dynamic dispatch);换句话说,VM将基于实例的运行期类型来选择要调用的覆写方法。覆写是面向对象编程技术的基础,并且是唯一没有被普遍劝阻的名字重用形式:

    Java代码  收藏代码
    1. class Base{  
    2.       public void f(){}  
    3. }  
    4. class Derived extends Base{  
    5.       public void f(){}  
    6. }  

     

     

    ★ 隐藏(hide)

           一个域、静态方法或成员类型可以分别隐藏(hide)在其超类中可访问到的具有相同名字(对方法而言就是相同的方法签名)的所有域、静态方法或成员类型。隐藏一个成员将阻止其被继承。

    Java代码  收藏代码
    1. class Base{  
    2.       public static void f(){}  
    3. }  
    4. class Derived extends Base  {  
    5.       private static void f(){}   //hides Base. f()  
    6. }  
     

    ★ 重载(overload) 
          在某个类中的方法可以重载(overload)另一个方法,只要它们具有相同的名字和不同的签名。由调用所指定的重载方法是在编译期选定的。

    Java代码  收藏代码
    1. class CircuitBreaker{  
    2.       public void f (int i){}    //int overloading  
    3.       public void f(String s){}   //String overloading  
    4. }  
     

    ★ 遮蔽(shadow)
         一个变量、方法或类型可以分别遮蔽(shadow)在一个闭合的文本范围内的具有相同名字的所有变量、方法或类型。如果一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;根据实体的不同,有时你根本就无法引用到它。

    Java代码  收藏代码
    1. class WhoKnows{  
    2.     static String sentence=”I don't know.”;  
    3.     public static void main(String[] args〕{  
    4.            String sentence=”I don't know.”;  //shadows static field  
    5.            System.out. println (sentence);  // prints local variable  
    6.     }  
    7. }  

          尽管遮蔽通常是被劝阻的,但是有一种通用的惯用法确实涉及遮蔽。构造器经常将来自其所在类的某个域名重用为一个参数,以传递这个命名域的值。这种惯用法并不是没有风险,但是大多数Java程序员都认为这种风格带来的实惠要超过
    其风险:

    Java代码  收藏代码
    1. class Belt{  
    2.       private find int size ;  //Parameter shadows Belt. size  
    3.       public Belt (int size){  
    4.            this. size=size;  
    5.       }  
    6. }  

     

    ★ 遮掩(obscure)

           一个变量可以遮掩具有相同名字的一个类型,只要它们都在同一个范围内:如果这个名字被用于变量与类型都被许可的范围,那么它将引用到变量上。相似地,一个变量或一个类型可以遮掩一个包。遮掩是唯一一种两个名字位于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类型。如果一个类型或一个包被遮掩了,那么你不能通过其简单
    名引用到它,除非是在这样一个上下文环境中,即语法只允许在其名字空间中出现一种名字。遵守命名习惯就可以极大地消除产生遮掩的可能性:

    Java代码  收藏代码
    1. public class Obscure{  
    2.       static String System;// Obscures type java.lang.System  
    3.       public static void main(String[] args)  
    4.             // Next line won't compile:System refers to static field  
    5.             System. out. println(“hello, obscure world!”);  
    6.       }  
    7. }  

    展开全文
  • 【解惑】Java动态绑定机制的内幕

    千次阅读 2016-08-04 11:35:33
    Java方法调用的过程... 这篇文章我们就将揭露JVM方法调用的静态(static binding) 和动态绑定机制(auto binding) 。     ★ 静态绑定机制 Java代码  //被调用的类  package hr.test;  class Father{

    在Java方法调用的过程中,JVM是如何知道调用的是哪个类的方法源代码? 这里面到底有什么内幕呢? 这篇文章我们就将揭露JVM方法调用的静态(static binding) 动态绑定机制(auto binding) 

     

     

    ★ 静态绑定机制

    Java代码  收藏代码
    1. //被调用的类  
    2. package hr.test;  
    3. class Father{  
    4.       public static void f1(){  
    5.               System.out.println("Father— f1()");  
    6.       }  
    7. }  
    8. //调用静态方法  
    9. import hr.test.Father;  
    10. public class StaticCall{  
    11.        public static void main(){  
    12.             Father.f1(); //调用静态方法  
    13.        }  
    14. }  

         上面的源代码中执行方法调用的语句(Father.f1())被编译器编译成了一条指令:invokestatic #13。我们看看JVM是如何处理这条指令的

         (1) 指令中的#13指的是StaticCall类的常量池中第13个常量表的索引项(关于常量池详见《Class文件内容及常量池 》)。这个常量表(CONSTATN_Methodref_info ) 记录的是方法f1信息的符号引用(包括f1所在的类名,方法名和返回类型)。JVM会首先根据这个符号引用找到方法f1所在的类的全限定名: hr.test.Father。

         (2) 紧接着JVM会加载、链接和初始化Father类。

         (3) 然后在Father类所在的方法区中找到f1()方法的直接地址,并将这个直接地址记录到StaticCall类的常量池索引为13的常量表中。这个过程叫常量池解析 ,以后再次调用Father.f1()时,将直接找到f1方法的字节码。

         (4) 完成了StaticCall类常量池索引项13的常量表的解析之后,JVM就可以调用f1()方法,并开始解释执行f1()方法中的指令了。

     

         通过上面的过程,我们发现经过常量池解析之后,JVM就能够确定要调用的f1()方法具体在内存的什么位置上了。实际上,这个信息在编译阶段就已经在StaticCall类的常量池中记录了下来。这种在编译阶段就能够确定调用哪个方法的方式,我们叫做 静态绑定机制 

     

         除了被static 修饰的静态方法,所有被private 修饰的私有方法、被final 修饰的禁止子类覆盖的方法都会被编译成invokestatic指令。另外所有类的初始化方法<init>和<clinit>会被编译成invokespecial指令。JVM会采用静态绑定机制来顺利的调用这些方法。

     

     

     

    ★ 动态绑定机制

    Java代码  收藏代码
    1. package hr.test;  
    2. //被调用的父类  
    3. class Father{  
    4.     public void f1(){  
    5.         System.out.println("father-f1()");  
    6.     }  
    7.         public void f1(int i){  
    8.                 System.out.println("father-f1()  para-int "+i);  
    9.         }  
    10. }  
    11. //被调用的子类  
    12. class Son extends Father{  
    13.     public void f1(){ //覆盖父类的方法  
    14.         System.out.println("Son-f1()");  
    15.     }  
    16.         public void f1(char c){  
    17.                 System.out.println("Son-s1() para-char "+c);  
    18.         }  
    19. }  
    20.   
    21. //调用方法  
    22. import hr.test.*;  
    23. public class AutoCall{  
    24.     public static void main(String[] args){  
    25.         Father father=new Son(); //多态  
    26.         father.f1(); //打印结果: Son-f1()  
    27.     }  
    28. }  

          上面的源代码中有三个重要的概念:多态(polymorphism) 方法覆盖 、方法重载 。打印的结果大家也都比较清楚,但是JVM是如何知道f.f1()调用的是子类Sun中方法而不是Father中的方法呢?在解释这个问题之前,我们首先简单的讲下JVM管理的一个非常重要的数据结构——方法表 

     

           在JVM加载类的同时,会在方法区中为这个类存放很多信息(详见《Java 虚拟机体系结构 》)。其中就有一个数据结构叫方法表。它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址 。下图是上面源代码中Father和Sun类在方法区中的方法表:

     

          上图中的方法表有两个特点:(1) 子类方法表中继承了父类的方法,比如Father extends Object。 (2) 相同的方法(相同的方法签名:方法名和参数列表)在所有类的方法表中的索引相同。比如Father方法表中的f1()和Son方法表中的f1()都位于各自方法表的第11项中。

     

          对于上面的源代码,编译器首先会把main方法编译成下面的字节码指令:

    多态调用的字节码指令代码  收藏代码
    1. 0  new hr.test.Son [13] //在堆中开辟一个Son对象的内存空间,并将对象引用压入操作数栈  
    2. 3  dup    
    3. 4  invokespecial #7 [15] // 调用初始化方法来初始化堆中的Son对象   
    4. 7  astore_1 //弹出操作数栈的Son对象引用压入局部变量1中  
    5. 8  aload_1 //取出局部变量1中的对象引用压入操作数栈  
    6. 9  invokevirtual #15 //调用f1()方法  
    7. 12  return  

           其中invokevirtual指令的详细调用过程是这样的:

           (1) invokevirtual指令中的#15指的是AutoCall类的常量池中第15个常量表的索引项。这个常量表(CONSTATN_Methodref_info ) 记录的是方法f1信息的符号引用(包括f1所在的类名,方法名和返回类型)。JVM会首先根据这个符号引用找到调用方法f1的类的全限定名: hr.test.Father。这是因为调用方法f1的类的对象father声明为Father类型。

           (2) 在Father类型的方法表中查找方法f1,如果找到,则将方法f1在方法表中的索引项11(如上图)记录到AutoCall类的常量池中第15个常量表中(常量池解析 )。这里有一点要注意:如果Father类型方法表中没有方法f1,那么即使Son类型中方法表有,编译的时候也通过不了。因为调用方法f1的类的对象father的声明为Father类型。

           (3) 在调用invokevirtual指令前有一个aload_1指令,它会将开始创建在堆中的Son对象的引用压入操作数栈。然后invokevirtual指令会根据这个Son对象的引用首先找到堆中的Son对象,然后进一步找到Son对象所属类型的方法表。过程如下图所示:

                        

          (4) 这是通过第(2)步中解析完成的#15常量表中的方法表的索引项11,可以定位到Son类型方法表中的方法f1(),然后通过直接地址找到该方法字节码所在的内存空间。

     

          很明显,根据对象(father)的声明类型(Father)还不能够确定调用方法f1的位置,必须根据father在堆中实际创建的对象类型Son来确定f1方法所在的位置。这种在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,我们叫做 动态绑定机制 

     

          上面的过程很清楚的反映出在方法覆盖的多态调用的情况下,JVM是如何定位到准确的方法的。但是下面的调用方法JVM是如何定位的呢?(仍然使用上面代码中的Father和Son类型)

    Java代码  收藏代码
    1. public class AutoCall{  
    2.        public static void main(String[] args){  
    3.              Father father=new Son();  
    4.              char c='a';  
    5.              father.f1(c); //打印结果:father-f1()  para-int 97  
    6.        }  
    7. }  

           问题是Fahter类型中并没有方法签名为f1(char)的方法呀。但打印结果显示JVM调用了Father类型中的f1(int)方法,并没有调用到Son类型中的f1(char)方法。

     

           根据上面详细阐述的调用过程,首先可以明确的是:JVM首先是根据对象father声明的类型Father来解析常量池的(也就是用Father方法表中的索引项来代替常量池中的符号引用)。如果Father中没有匹配到"合适" 的方法,就无法进行常量池解析,这在编译阶段就通过不了。

          那么什么叫"合适"的方法呢?当然,方法签名完全一样的方法自然是合适的。但是如果方法中的参数类型在声明的类型中并不能找到呢?比如上面的代码中调用father.f1(char),Father类型并没有f1(char)的方法签名。实际上,JVM会找到一种“凑合”的办法,就是通过 参数的自动转型 来找 到“合适”的 方法。比如char可以通过自动转型成int,那么Father类中就可以匹配到这个方法了 (关于Java的自动转型问题可以参见《【解惑】Java类型间的转型 》)。但是还有一个问题,如果通过自动转型发现可以“凑合”出两个方法的话怎么办?比如下面的代码:

    Java代码  收藏代码
    1. class Father{  
    2.     public void f1(Object o){  
    3.         System.out.println("Object");  
    4.     }  
    5.     public void f1(double[] d){  
    6.         System.out.println("double[]");  
    7.     }  
    8.       
    9. }  
    10. public class Demo{  
    11.     public static void main(String[] args) {  
    12.         new Father().f1(null); //打印结果: double[]  
    13.     }  
    14. }  

            null可以引用于任何的引用类型,那么JVM如何确定“合适”的方法呢。一个很重要的标准就是:如果一个方法可以接受传递给另一个方法的任何参数,那么第一个方法就相对不合适。比如上面的代码: 任何传递给f1(double[])方法的参数都可以传递给f1(Object)方法,而反之却不行,那么f1(double[])方法就更合适。因此JVM就会调用这个更合适的方法。

     

     

    ★ 总结

     

          (1) 所有私有方法、静态方法、构造器及初始化方法<clinit>都是采用静态绑定机制。在编译器阶段就已经指明了调用方法在常量池中的符号引用,JVM运行的时候只需要进行一次常量池解析即可。


          (2) 类对象方法的调用必须在运行过程中采用动态绑定机制。

               首先,根据对象的声明类型(对象引用的类型)找到“合适”的方法。具体步骤如下:

               ① 如果能在声明类型中匹配到方法签名完全一样(参数类型一致)的方法,那么这个方法是最合适的。

               ② 在第①条不能满足的情况下,寻找可以“凑合”的方法。标准就是通过将参数类型进行自动转型之后再进行匹配。如果匹配到多个自动转型后的方法签名f(A)和f(B),则用下面的标准来确定合适的方法:传递给f(A)方法的参数都可以传递给f(B),则f(A)最合适。反之f(B)最合适 。

     

               ③ 如果仍然在声明类型中找不到“合适”的方法,则编译阶段就无法通过。

     

               然后,根据在堆中创建对象的实际类型找到对应的方法表,从中确定具体的方法在内存中的位置。

     

     

     

    ★ 覆写(override)

           一个实例方法可以覆写(override)在其超类中可访问到的具有相同签名的所有实例方法,从而使能了动态分派(dynamic dispatch);换句话说,VM将基于实例的运行期类型来选择要调用的覆写方法。覆写是面向对象编程技术的基础,并且是唯一没有被普遍劝阻的名字重用形式:

    Java代码  收藏代码
    1. class Base{  
    2.       public void f(){}  
    3. }  
    4. class Derived extends Base{  
    5.       public void f(){}  
    6. }  

     

     

    ★ 隐藏(hide)

           一个域、静态方法或成员类型可以分别隐藏(hide)在其超类中可访问到的具有相同名字(对方法而言就是相同的方法签名)的所有域、静态方法或成员类型。隐藏一个成员将阻止其被继承。

    Java代码  收藏代码
    1. class Base{  
    2.       public static void f(){}  
    3. }  
    4. class Derived extends Base  {  
    5.       private static void f(){}   //hides Base. f()  
    6. }  
     

    ★ 重载(overload) 
          在某个类中的方法可以重载(overload)另一个方法,只要它们具有相同的名字和不同的签名。由调用所指定的重载方法是在编译期选定的。

    Java代码  收藏代码
    1. class CircuitBreaker{  
    2.       public void f (int i){}    //int overloading  
    3.       public void f(String s){}   //String overloading  
    4. }  
     

    ★ 遮蔽(shadow)
         一个变量、方法或类型可以分别遮蔽(shadow)在一个闭合的文本范围内的具有相同名字的所有变量、方法或类型。如果一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;根据实体的不同,有时你根本就无法引用到它。

    Java代码  收藏代码
    1. class WhoKnows{  
    2.     static String sentence=”I don't know.”;  
    3.     public static void main(String[] args〕{  
    4.            String sentence=”I don't know.”;  //shadows static field  
    5.            System.out. println (sentence);  // prints local variable  
    6.     }  
    7. }  

          尽管遮蔽通常是被劝阻的,但是有一种通用的惯用法确实涉及遮蔽。构造器经常将来自其所在类的某个域名重用为一个参数,以传递这个命名域的值。这种惯用法并不是没有风险,但是大多数Java程序员都认为这种风格带来的实惠要超过
    其风险:

    Java代码  收藏代码
    1. class Belt{  
    2.       private find int size ;  //Parameter shadows Belt. size  
    3.       public Belt (int size){  
    4.            this. size=size;  
    5.       }  
    6. }  

     

    ★ 遮掩(obscure)

           一个变量可以遮掩具有相同名字的一个类型,只要它们都在同一个范围内:如果这个名字被用于变量与类型都被许可的范围,那么它将引用到变量上。相似地,一个变量或一个类型可以遮掩一个包。遮掩是唯一一种两个名字位于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类型。如果一个类型或一个包被遮掩了,那么你不能通过其简单
    名引用到它,除非是在这样一个上下文环境中,即语法只允许在其名字空间中出现一种名字。遵守命名习惯就可以极大地消除产生遮掩的可能性:

    Java代码  收藏代码
    1. public class Obscure{  
    2.       static String System;// Obscures type java.lang.System  
    3.       public static void main(String[] args)  
    4.             // Next line won't compile:System refers to static field  
    5.             System. out. println(“hello, obscure world!”);  
    6.       }  
    7. }  


    附带一个基础面试题可以用来理解动态绑定机制 结合上面所说的 分析一下你自己了解动态绑定的程度

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    public class OverrideTest {
    	public void f(Collection c) {
    		System.out.println("Collection");
    	}
    
    	public void f(List m) {
    		System.out.println("List");
    	}
    
    	public void f(Set m) {
    		System.out.println("Set");
    	}
    
    	public static void main(String[] args) {
    		OverrideTest overrideTest = new OverrideTest();
    		Collection[] c = { new ArrayList(), new HashSet() };
    		for (int i = 0; i < c.length; i++) {
    			overrideTest.f(c[i]);
    		}
    	}
    }


    展开全文
  • 详解Java动态绑定和静态绑定机制

    千次阅读 2019-04-22 09:27:42
    https://jiayouxujin.github.io/2019/04/20/java动态绑定和静态绑定详解/ 这一次,彻底搞懂动态绑定和静态绑定机制
  • Java动态绑定机制

    2015-03-12 18:04:25
    Java动态绑定又称为运行时绑定。意思就是说,程序会在运行的时候自动选择调用哪儿个方法。 一、动态绑定的过程:  例子: 1 public class Son extends Father2 Son son = new Son();3 son.method(); ...
  • Java动态绑定机制(非常重要) Java重要特性:动态绑定机制 运行时绑定也叫动态绑定,它是一种调用对象方法的机制。Java调用对象方法时,一般采用运行时绑定机制。 1.Java的方法调用过程 编译器查看对象的声明...
  • JAVA多态的动态绑定机制

    千次阅读 2018-10-19 17:27:51
    对于多态,大家基本上都很熟悉,日常开发用的也挺多,一句话概括:父类引用指向...为了弄清楚多态,我们需要引入jvm方法调用的静态和动态绑定机制的概念, jvm静态绑定机制 Public class Utils{ private stati...
  • JAVA虚拟机调用一个类方法时,它会基于...动态绑定为解决实际的业务问题提供了很大的灵活性,是一种非常优美的机制。 1 JAVA对象模型 JAVA虚拟机规范并没有规定JAVA对象在堆里是如何表示的。对象的内部表示也影响...
  • java多态总结以及动态绑定机制

    千次阅读 2016-07-19 00:35:10
    1.继承体现了多态,JAVA里没有多继承,一个类之能有一个父类。而继承的表现就是多态。一个父类可以有多个子类,而在子类里可以重写父类的方法(例如方法print()),这样每个子类里重写的代码不一样,自然表现形式就...
  • java中的动态绑定机制

    2011-06-16 10:18:00
    动态绑定工作机制如下:假设类C1,C2,...Cn和对象o,C1是C2的子类,C2是C3的子类,...,Cn-1是Cn的子类,o是类C1的实例,对象o调用一个方法p,java虚拟机一次在类C1,C2,...,Cn-1中查找方法p的实现,直到找到为止,...
  • java 动态绑定 以及 内部实现机制

    千次阅读 2012-04-28 12:25:38
    java来说,绑定分为静态绑定动态绑定;或者叫做前期绑定和后期绑定。  (1)静态绑定:  在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。例如:C。  针对java简单的可以理解为程序编译...
  • java动态绑定内部实现

    2014-09-29 12:09:31
    JAVA动态绑定的内部实现机制  JAVA虚拟机调用一个类方法时,它会基于对象引用的类型(通常在编译时可知)来选择所调用的方法。相反,当虚拟机调用一个实例方法时,它会基于对象实际的类型(只能在运行时得知)来选择...
  • Java静态绑定动态绑定的理解

    千次阅读 2018-01-16 12:48:34
    前言本人在学习虚拟机类加载机制时看到,java中类型的加载、连接和初始化过程都是在程序运行时期完成的。这种策略虽然会令类加载时稍微增加一些性能开销,但是会为java应用程序提供高度的灵活性...java绑定程序绑定的概
  • Java 动态绑定详解

    千次阅读 2011-03-20 18:54:00
    java来说,绑定分为静态绑定动态绑定;或者叫做前期绑定和后期绑定静态绑定:在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。例如:C。针对java简单的可以理解为程序编译期的绑定;这里特别说明...
  • Java数据模型及动态绑定机制

    千次阅读 2013-10-07 10:30:13
    转自:http://blog.csdn.net/sureyonder/article/details/5569617
  • Java静态绑定动态绑定

    万次阅读 多人点赞 2014-04-19 00:31:12
    java来说,绑定分为静态绑定动态绑定;或者叫做前期绑定和后期绑定. 静态绑定: 在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序...
  • Java多态机制的实现依赖于其动态绑定。 (本文默认读者已经了解Java程序的编译和运行时问题。)1、程序绑定的概念 绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。  对Java来说,绑定分为静态绑定...
  • Java动态绑定和静态绑定

    千次阅读 多人点赞 2017-09-14 17:05:16
    Java来说,绑定分为静态绑定动态绑定;或者叫做前期绑定和后期绑定。静态绑定在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。例如:C。针对Java,可以简单地理解为程序编译期的绑定。这里特别...
  • java来说,绑定分为前期绑定(静态绑定)与后期绑定动态绑定)。静态绑定: 在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序实现。 ...
  • java静态绑定&动态绑定详解

    千次阅读 2016-08-16 19:20:46
    java来说,绑定分为静态绑定动态绑定;或者叫做前期绑定和后期绑定静态绑定: 在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。例如:C。 针对java简单的可以理解为程序编译期的绑定;这里特别...
  • 若在程序执行前进行绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定。读者可能以前从来没有听说过这个术语,因为它是面向过程的语言中不需要选择默认的绑定方式。例如,C只有一种方法调用,那就是前期...
  • 静态绑定(前期绑定): ...动态绑定(动态绑定): 在运行时根据具体对象的类型进行绑定。 若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型, 并分别调用适当的方法。也
  • java动态绑定与静态绑定 首先是方法的参数是父类对象,传入子类对象是否可行 然后引出Parent p = new Children(); 这句代码不是很理解,google的过程中引出向上转型 要理解向上转型又引出了动态绑定 从...
  • java动态绑定在属性和方法上的不同

    千次阅读 2016-05-09 16:06:37
    Java调用对象方法时,一般采用运行时绑定机制。  1.Java的方法调用过程  编译器查看对象的声明类型和方法名(对象变量的声明类型)。通过声明类型找到方法列表。  编译器查看调用方法时提供的参数类型。  ...
  • java中的动态绑定和静态绑定
  • 向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而...多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 179,302
精华内容 71,720
关键字:

java的动态绑定机制

java 订阅