精华内容
下载资源
问答
  • 主要介绍了java动态绑定和静态绑定用法,结合实例形式详细分析了java动态绑定与静态绑定相关概念、原理、实现方法及使用注意事项,需要的朋友可以参考下
  • 深入理解C++的动态绑定和静态绑定 静态绑定:绑定的对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发生在编译期。 动态绑定:绑定的对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发生在...
  • 动态绑定和静态绑定

    千次阅读 2017-12-11 20:04:53
    动态绑定动态绑定是指编译器在编译阶段不知道要调用哪个方法Parent父类,父类有两个方法一个是类方法shout,一个是实例方法saypublic class Parent { String name; public static void shout(){ System.out.println...

    动态绑定

    动态绑定是指编译器在编译阶段不知道要调用哪个方法

    Parent父类,父类有两个方法一个是类方法shout,一个是实例方法say

    public class Parent {
        String name;
        public static void shout(){
            System.out.println("我是父类shout静态方法");
        }
    
        public void say(){
            System.out.println("我是父类say方法");
        }
    }

    Son子类继承父类,并重写了say实例方法

    public class Son extends Parent {
        String name;
        @Override
        public void say() {
            System.out.println("我是子类say");
        }
    }

    测试函数

    public class MainTest {
        public static void main(String[] args) {
            Parent parent = new Parent();
            Parent son = new Son();
            parent.say();
            son.say();
        }
    }

    声明的两个对象都是parent类型,但运行时他们指向的都是不同的对象类型

    虽然调用的是say方法但是编译器在编译阶段不能确定调用的是子类还是父类的方法,方法的动态绑定是基于实际的对象类型,而不是它们声明的对象引用类型

    静态绑定

    我们知道静态方法不能被重写,为什么呢,其实这就和静态绑定有关.编译器在编译期间就能完成绑定的叫做静态绑定也叫前期绑定,基本上实例方法都在运行时绑定,静态方法是静态绑定在编译期间就已经决定的

    给父类和子类同时加上静态方法

    public static void shout(){
            System.out.println("我是父类shout静态方法");
    }
    public static void shout(){
            System.out.println("我是子类shout静态方法");
    }

    测试

    public static void main(String[] args) {
            Parent parent = new Parent();
            Parent son = new Son();
            parent.shout();
            son.shout();
        }

    运行结果

    我是父类shout静态方法
    我是父类shout静态方法

    虽然java允许重写静态方法但是无效,调用的静态方法是父类的静态方法

    成员变量的静态绑定

    父类添加属性

    String name="parent";

    子类添加属性

    String name="son";

    测试

    public static void main(String[] args) {
            Parent parent = new Parent();
            Parent son = new Son();
            System.out.println(son.name);
            System.out.println(parent.name);
        }

    运行结果

    parent
    parent

    总结一下静态绑定存在于以下几种情况
    1. 类方法
    2. 私有方法(不会被继承)
    3. 访问成员变量

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

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

    https://jiayouxujin.github.io/2019/04/20/java动态绑定和静态绑定详解/
    这一次,彻底搞懂动态绑定和静态绑定机制

    展开全文
  • C++在面向对象编程中,存在着静态绑定和动态绑定的定义,本节即是主要讲述这两点区分。  我是在一个类的继承体系中分析的,因此下面所说的对象一般是指一个类的实例。  首先我们需要明确几个名词定义:  静态...
  • Java的动态绑定和静态绑定

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

    引言

    在Java方法调用的过程中,JVM是如何知道调用的是哪个类的方法源代码呢?这就涉及到程序绑定这个概念。

    程序绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对Java来说,绑定分为静态绑定动态绑定;或者叫做前期绑定和后期绑定。

    静态绑定

    在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。例如:C。

    针对Java,可以简单地理解为程序编译期的绑定。这里特别说明一点,Java当中的方法只有finalstaticprivate构造方法是静态绑定。

    请看如下Java代码:

    //被调用的类
    package hr.test;
    class Father{
          public static void f1(){
                  System.out.println("Father— f1()");
          }
    }
    //调用静态方法
    import hr.test.Father;
    public class StaticCall{
           public static void main(){
                Father.f1(); //调用静态方法
           }
    }

    上面的源代码中执行方法调用的语句Father.f1()被编译器编译成了一条指令:invokestatic #13

    我们看看JVM是如何处理这条指令的:

    (1) 指令中的#13指的是StaticCall类的常量池中第13个常量表的索引项(关于常量池详见《Class文件内容及常量池 》)。这个常量表(CONSTANT_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代码:

    package hr.test;
    //被调用的父类
    class Father{
        public void f1(){
            System.out.println("father-f1()");
        }
            public void f1(int i){
                    System.out.println("father-f1()  para-int "+i);
            }
    }
    //被调用的子类
    class Son extends Father{
        public void f1(){ //覆盖父类的方法
            System.out.println("Son-f1()");
        }
            public void f1(char c){
                    System.out.println("Son-s1() para-char "+c);
            }
    }
    
    //调用方法
    import hr.test.*;
    public class AutoCall{
        public static void main(String[] args){
            Father father=new Son(); //多态
            father.f1(); //打印结果: Son-f1()
        }
    }

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

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

    下图是上面源代码中Father和Sun类在方法区中的方法表:
    这里写图片描述

    可以看出方法表有两个特点:

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

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

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

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

    (1) invokevirtual指令中的#15指的是AutoCall类的常量池中第15个常量表的索引项。这个常量表(CONSTANT_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类型)

    public class AutoCall{
           public static void main(String[] args){
                 Father father=new Son();
                 char c='a';
                 father.f1(c); //打印结果:father-f1()  para-int 97
           }
    }

    问题是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类型间的转型》)。但是还有一个问题,如果通过自动转型发现可以“凑合”出两个方法的话怎么办?比如下面的代码:

    class Father{
        public void f1(Object o){
            System.out.println("Object");
        }
        public void f1(double[] d){
            System.out.println("double[]");
        }
    
    }
    public class Demo{
        public static void main(String[] args) {
            new Father().f1(null); //打印结果: double[]
        }
    }

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

    属性是动态绑定的吗

    首先来看代码:

    public class Father {   
    
      protected String name="父亲属性";   
         
      public void method() {   
        System.out.println("父类方法,对象类型:" + this.getClass());   
      }   
    }   
         
    public class Son extends Father {   
      protected String name="儿子属性";   
         
      public void method() {   
        System.out.println("子类方法,对象类型:" + this.getClass());   
      }   
         
      public static void main(String[] args) {   
        Father sample = new Son();//向上转型   
        System.out.println("调用的成员:"+sample.name);   
      }   
    }

    运行结果:

    调用的成员为父亲的属性

    这个结果表明,子类的对象(由父类的引用handle)调用到的是父类的成员变量。所以必须明确,运行时(动态)绑定针对的范畴只是对象的方法。

    现在试图调用子类的成员变量name,该怎么做?最简单的办法是将该成员变量封装成方法getter形式:

    public class Father {   
      protected String name = "父亲属性";   
      public String getName() {   
        return name;   
      }   
      public void method() {   
        System.out.println("父类方法,对象类型:" + this.getClass());   
      }   
    }   
         
    public class Son extends Father {   
      protected String name="儿子属性";   
         
      public String getName() {   
        return name;   
      }   
         
      public void method() {   
        System.out.println("子类方法,对象类型:" + this.getClass());   
      }   
         
      public static void main(String[] args) {   
        Father sample = new Son();//向上转型   
        System.out.println("调用的成员:"+sample.getName());   
      }   
    }

    常见面试问题

    重写和重载的区别

    重写(override)

    Java的method overriding则发生在虚方法之间。调用虚方法时,Java采用的是延迟绑定 / 动态分派的语义,根据被调用对象(receiver)的实际类型来决定选择哪个版本的虚方法。

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

    class Base{
          public void f(){}
    }
    class Derived extends Base{
          public void f(){}
    }

    重写的特点

      1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果

      2、覆盖的方法的返回值必须和被覆盖的方法的返回一致;

      3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;

      4、被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

    重载(overload)

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

    class CircuitBreaker{
          public void f (int i){}    //int overloading
          public void f(String s){}   //String overloading
    }

    有一个值得注意的地方:

    • 所谓的重载是静态绑定的也只是指在编译期f (int i)f(String s)方法签名不同,运行期我们知道调用的是f (int i)还是f(String s)。但是调用的是哪个类的方法还要根据实例对象去判断。

    例如如下代码:

    public class Test {
        public static void main(String[] args) {
            Animal animal = new Animal();
            animal.eat();
            animal.eat(1);
            animal.eat("1");
        }
    }
    
    class Animal {
        public void eat() {
    
        }
    
        public void eat(int a) {
    
        }
    
        public void eat(String a) {
    
        }
    }

    对应的字节码:

    public class com.example.tsnt.Test {
      public com.example.tsnt.Test();
        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 com/example/tsnt/Animal
           3: dup
           4: invokespecial #3                  // Method com/example/tsnt/Animal."<init>":()V
           7: astore_1
           8: aload_1
           9: invokevirtual #4                  // Method com/example/tsnt/Animal.eat:()V
          12: aload_1
          13: iconst_1
          14: invokevirtual #5                  // Method com/example/tsnt/Animal.eat:(I)V
          17: aload_1
          18: ldc           #6                  // String 1
          20: invokevirtual #7                  // Method com/example/tsnt/Animal.eat:(Ljava/lang/String;)V
          23: return
    }

    可以看到其中这几行:

           9: invokevirtual #4                  // Method com/example/tsnt/Animal.eat:()V
          14: invokevirtual #5                  // Method com/example/tsnt/Animal.eat:(I)V
          20: invokevirtual #7                  // Method com/example/tsnt/Animal.eat:(Ljava/lang/String;)V

    对于调用哪个重载的方法,编译阶段已经确定下来了,但是对于调用哪个类的方法编译阶段没有确定下来,所以显示的是invokevirtual,这个要等到运行时才能确定。

    重载的特点

      1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int, float), 但是不能为fun(int, int))

      2、不能通过访问权限、返回类型、抛出的异常进行重载;

      3、方法的异常类型和数目不会对重载造成影响;

      4、对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。

    静态属性和静态方法能否被继承,能否重写

    静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成的,不需继承机制就可以调用如果子类里面定义了和父类声明一样的静态方法和属性,那么这时候父类的静态方法或属性称之为“隐藏”,你如果想要调用父类的静态方法和属性,直接通过父类名.方法名或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在“隐藏”的这种情况。

    多态之所以能够实现是依赖于继承和重写 、重载(继承和重写最为关键)。有了继承和重写就可以 实现父类的引用可以指向不同子类的对象。重写的功能是:“重写”后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。

    结论:静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能够被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态的方法可以被继承和重写,因此可以实现多态。

    附:
    查看字节码的方法:查看Java字节码的方法

    参考:
    1.java中静态属性和和静态方法的继承问题 以及多态的实质
    2.java的动态绑定与静态绑定
    3.【解惑】Java动态绑定机制的内幕
    4.Java的函数重载为什么采取静态静态绑定而非动态绑定?

    展开全文
  • PAGE / NUMPAGES 动态绑定与静态绑定 当子类(Y)同时覆盖了父类(X)的属性方法时为什么X s = new Y; 这里的s只可以调用Y中覆盖X的方法而不能使用Y中覆盖X的属性这也是疑惑的关键 我先给出一个结论父类引用指向子类...
  • 深入理解C++的动态绑定和静态绑定.pdf
  • 为了支持c++的多态性,才用了动态绑定和静态绑定。理解他们的区别有助于更好的理解多态性,以及在编程的过程中避免犯错误。需要理解四个名词:1、对象的静态类型:对象在声明时采用的类型。是在编译期确定的。2、...
  • 对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定后期绑定。  静态绑定:  在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。例如:C。  针对java简单的可以理解为程序编译期的...
  • 对于页面中已有的静态元素 select1中的option,对其绑定click事件一般主要可以通过以下方法: 第一种: $(".select1 option").bind("click",function(){}); 第二种: $(".select1 option").click(function(){}); ...

    <select class="select1">
    <option value="1">1</option>
    <option value="2"></option>
    </select>
    
    <select class="select2">
    </select>

    对于页面中已有的静态元素 select1中的option,对其绑定click事件一般主要可以通过以下方法:

    第一种:

    $(".select1 option").bind("click",function(){});

    第二种:

    $(".select1 option").click(function(){});

    但是对于页面中动态添加的元素,如select2中的option(当前不存在),对其绑定click事件的方法一般为:

    第一种:

    $(".select2").delegate("option","click",function(){});//注意"option"一定要写在delegate函数里面

    第二种:

    $(".select2").on("click","option",function(){});//注意"option"一定要写在on函数里面

    当然对于静态元素也可以采用针对动态元素的方法
    $(".select1").delegate("option","click",function(){});
    或:$(".select1 option").delegate("click",function(){});//这里"option"的位置不重要

    $(".select1").on("click","option",function(){});
    $(".select1 option").on("click",function(){});//这里"option"的位置一样不重要
    这里注意一下delegate函数和on函数的参数位置即可。
    展开全文
  • 1、 根据拓扑完成上图 ,可以配置pc的地址为192.168.1.1-192.168.1.4 ...验证试验,静态绑定的PC可以访问PC3 ,其他不可以访问PC3 ,证明试验成功 。 转载于:https://blog.51cto.com/ronning/2116012
  • java的动态绑定和静态绑定

    千次阅读 2018-07-23 10:58:10
    1.当子类父类存在同一个方法,子类重写了父类的方法,程序在运行时调用的是父类的方法还是子类的重写方法呢(尤其是存在向上类型转换的情况)? 2.当一个类中存在方法名相同但参数不同(重载)的方法,程序在执行...
  • 动态绑定和静态绑定是[[多态]]的一个内容 就是想解决向上转型的子类对象,运行时调用的是父类的方法变量,还是子类的方法变量。 结论 参考了大量博客,我认为除了成员方法,其余全都是静态绑定静态绑定包括:...
  • 详细介绍了java动态绑定和静态绑定的区别
  • 为了支持c++的多态性,才用了动态绑定和静态绑定。理解他们的区别有助于更好的理解多态性,以及在编程的过程中避免犯错误。 需要理解四个名词: 1、对象的静态类型:对象在声明时采用的类型。是在编译期确定的。 ...
  • 在一般的程序设计语言中绑定是在编译时进行的,叫做静态绑定动态绑定则是在运行时进行的,因此,一个给定的过程调用代码结合直到调用发生时才进行。 动态绑定是类的继承以及多态相联系的。在继承关系中,子类...
  • 主要介绍了Java 静态绑定动态绑定深入分析的相关资料,这里对java 的动态绑定和静态绑定做了详细的介绍,对其进行总结整理,需要的朋友可以参考下
  • 静态绑定和动态绑定总结

    千次阅读 2017-11-05 16:22:20
    静态绑定和动态绑定 ^_^
  • 对java来说,绑定分为前期绑定(静态绑定)与后期绑定(动态绑定)。静态绑定: 在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序实现。 ...
  • Java虚拟机的动态绑定与静态绑定 大家可能都知道Java面向对象的三大特性,封装,继承,多态,其中动态绑定就与多态有关,那什么是动态绑定呢? 1.动态绑定(auto binding):也叫后期绑定,在运行时,虚拟机根据具体...
  • 静态绑定和动态绑定的区别

    千次阅读 2019-12-13 21:19:52
    静态绑定和动态绑定的区别 A.静态绑定是绑定类,动态绑定是绑定对象 B.静态绑定在编译期绑定,动态绑定在运行期绑定 C.静态绑定==不可以被继承或继承后不能被重写 ...
  • 静态绑定动态绑定

    千次阅读 多人点赞 2018-02-18 19:30:42
    绑定分为静态绑定(前期绑定)和动态绑定(后期绑定)。   二:静态绑定  静态绑定(前期绑定)是指:在程序运行前就已经知道方法是属于那个类的,在编译的时候就可以连接到类的中,定位到这个方法。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 315,436
精华内容 126,174
关键字:

动态绑定和静态绑定