java中的静态绑定与动态绑定

个人学习记录,欢迎大家指导

什么是多态?

一个引用变量,它可以引用任何子类的对象,这个引用变量是多态的。

绑定

将一个方法调用与对应方法主体关联起来被称为绑定。(也就是,执行一条方法调用语句时所对应执行的方法体,叫做该方法体和这条方法调用语句绑定了)

动态绑定

来看一段代码

public class Parent {

    public void m(){
        System.out.println("父类方法执行");
    }

    public static void main(String[] arg){
        List<Parent> list = Arrays.asList(new A(),new B(),new C());
        for(Parent p : list){
            p.m();
        }
    }
}

class A extends Parent{
    public void m(){
        System.out.println("A类方法执行");
    }
}

class B extends Parent{
    public void m(){
        System.out.println("B类方法执行");
    }
}

class C extends Parent{
    public void m(){
        System.out.println("C类方法执行");
    }
}

output:
A类方法执行
B类方法执行
C类方法执行

何为动态绑定?
上述代码中,第一次循环,p变量接受一个A对象,p.m()语句调用A的m()方法,第二次循环时,p变量接受一个B对象,p.m()语句调用B的m()方法,第三次循环时,p变量接受一个C对象,p.m()语句调用C的m()方法,3次循环都是执行的p.m()语句,每次循环p.m()语句绑定的方法体不一样,这种运行时根据变量p实际引用对象动态绑定方法体的方式,叫做动态绑定。动态绑定是为了执行子类重写的方法。

静态绑定

对于static、final、private修饰的方法,以及构造方法,这些方法不可被子类重写,它们采用静态绑定。

方法调用的具体过程

看一段代码

public class Fu {

    public void m(Object obj){
        System.out.println("父类Object方法执行");
    }

    public static void main(String[] args){
        Fu f = new Zi();
        f.m(1);
    }

}

class Zi extends Fu{

    public void m(Object obj){
        System.out.println("子类Object方法执行");
    }

    public void m(Integer i){
        System.out.println("子类Integer方法执行");
    }
}

output:
子类Object方法执行

为啥选择子类的m(Object)方法执行,而不是m(Integer)方法执行呢,你肯定想说因为向上转型,不能执行子类特有的方法,确实是这样,但为什么呢?

  • 来看一下方法调用的过程
    编译期:
    1、编译器根据变量f声明的类型Fu,去Fu类中查找名字为m的方法。
    2、此时可能找到多个名为m的方法,因为方法是可以重载的,编译器再根据调用方法时提供的参数类型,去找到最匹配的方法,在这里是m(Object)方法,
    这个过程叫做重载解析。重载解析后,编译器获得了方法的签名(方法名+形参类型)。
    3、如果方法是被static、final、private修饰的,该方法不能被重写,f.m(1)运行时,无论f引用什么子类对象,都是执行的父类的方法,此时可以确定运行时无论f的对象是什么都是执行父类中的方法,绑定Fu类中的m(Object)方法。此为前期绑定,也叫静态绑定,是编译期时发生的绑定。
    4、如果方法不被static、final、private修饰,编译器采用动态绑定,记录方法签名m(Object)。
    运行期:
    执行f.m(1)字节码时
    1、如果是静态绑定,直接执行绑定的方法,不关心对象类型。
    2、如果是动态绑定,编译期只确定了方法签名,方法所在类未确定,根据f引用对象的实际类型,去该类型中找到签名为m(Object)的方法,执行该方法,此时运行时才把方法调用与具体方法体关联起来。

动态绑定就是运行的时候才决定执行哪个方法体,静态绑定在编译期就确定了要执行的方法体。
动态绑定在编译期确定执行的方法签名,在运行期确定执行哪个类的方法,实现自由执行子类重写的方法。静态绑定在编译期决定执行哪个类中的哪个方法,因为这些方法不能被重写,子类对象调用也是调用的父类中的,可以明确方法体,对于这类不能重写的方法,采用静态绑定性能更好,虽然采用动态绑定也能实现。

一个静态绑定的例子

public class Fu {

    private void m(){
        System.out.println("父类private方法执行");
    }

    public static void main(String[] args){
        Fu f = new Zi();
        f.m();
    }
}

public class Zi extends Fu{

    public void m(){
        System.out.println("子类public方法执行");
    }

    public static void main(String[] args){
        Fu f = new Zi();
       // f.m(); 无法编译
    }
}

output:
父类private方法执行

补充一个继承泛型类的例子

public class GenericClazz<T> {

    public void m(T t){
        System.out.println("泛型类中m()方法执行");
    }
}

public class Zi extends GenericClazz<Integer>{

    public void m(Integer i){
        System.out.println("子类中m()方法执行");
    }

//    public void m(Object i){
//        System.out.println("子类中m()方法执行");
//    } 无法编译

    public static void main(String[] args){
        GenericClazz f = new Zi();
        f.m(1);
    }
}

output:
子类中m()方法执行

泛型类中,由于有泛型擦除,T实际会被在编译时替换为Object类型,那么Zi类继承GenericClazz类就是继承的m(Object)方法,而m(Integer)是对m(Object)的重载而不是重写,在用f.m(1)调用时,按照上面的理论,运行时应该是调用的Zi类中的m(Object)方法,而m(Object)子类没有重写,应该走父类语句,但是这里却走了子类的m(Integer)方法,原因是编译器自动在子类做了m(Object)方法的重写,会执行类似下面的语句:

public void m(Object t){
    m((Integer) t);
}

让m(Integer)看起来就像是重写的父类的m(Integer)一样,并且还关闭了子类对m(Object)的手动重写,可以看到上面手动重写m(Object)编译是报错的。

posted @ 2024-11-13 22:52  我爱吃炸鸡  阅读(75)  评论(0编辑  收藏  举报