欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

$2.4、方法在字节码中的调用形式及重载和复写的区别

程序员文章站 2022-04-18 19:27:02
...

方法在字节码中的调用指令,主要分为5种:

1、invokeinterface:执行接口中的方法,该指令是运行时的指令,具体去找实现类对应的方法

2、invokestatic:执行静态方法

3、invokespecial:调用私有方法,父类方法(父类的普通方法、父类构造方法),构造方法调用的指令,是编译时就可以确定的调用关系

4、invokevirual:虚方法,需要在运行时才确定的执行指令,虚方法要看方法的使用者是谁,如果是申明类型,那么方法的执行只看传入的参数申明类型是否满足方法的参数要求--》重载;如果是实际类型,那么先看实际类型中是否有调用的方法(方法的描述符和权限满足)。有即调用,没有则向上往父类寻找--》多态

5、invokedynamic:jdk1.7以后加类的动态机制

我们主要介绍iinvokeviurual指令的流程

我们先看如下代码和输出:

public class MyTest3 {
    //方法重载,只关注方法的描述符:名称和参数(类型/个数)
    public void test(Parent p) {
        System.out.println("p invoke");
    }

    public void test(Son son) {
        System.out.println("son invoke");
    }

    public void test(Girl g) {
        System.out.println("girl invoke");
    }

    //重载是静态指派,只看申明类型是否满足方法的参数
    public static void main(String[] args) {
        //申明与实际类型一样,都是MyTest3,因为是重载
        //静态分配,传入的参数看申明类型
        MyTest3 myTest3 = new MyTest3();
        Parent p = new Son();
        Parent p2 = new Girl();

        /**
         * 方法的调用,test方法的使用者是myTest3
         * 调用test方法是对MyTest3的重载
         * jvm中,方法的重载是静态指派,即在编译时期,方法的调用关系已确定
         * 关注方法的调用参数是否满足传入的静态类型
         */

        myTest3.test(p);
        myTest3.test(p2);
    }

}

class Parent {

}

class Son extends Parent {

}

class Girl extends Parent {

}

查看对应的字节码指令:

$2.4、方法在字节码中的调用形式及重载和复写的区别对应的虚方法指令,我们根据test方法的使用者分析是:MyTest3,该类的类型是申明类型,因为对应的返回就是new MyTest3().,调用test方法就是对方法的重载,根据jvm,重载是静态的,因此真正调用方法就是传递类的申明类型,因此输出就是传入Parent的参数方法$2.4、方法在字节码中的调用形式及重载和复写的区别

换句话说,重载是静态的指令执行,关注申明类型

我们再看下复写的代码:

public class MyTest4 {

    public static void main(String[] args) {
        Animal a1 = new Dog();
        //申明与实际类型不一样,动态分配,找实际类型的方法,实际类型没有,再往上找父类
        Animal a2 = new Cat();
        a1.test();
        a2.test();
        /**
         * test的使用者animal,但是animal的申明类型和实际类型是不一样的
         * 因此是多态的特性,在jvm中,多态是动态类型分配的
         * invokevirual的多态查找步骤是
         * 拿到栈顶元素的实际类型,去实际类型中去查找是否有一样的方法描述,并且有访问权限
         *在例子中的实际类型是dog,dog中的test方法是可以访问的,因此调用dog的
         * 加入dog中没有,就依次往父类上寻找
         */
    }
}

class Animal {

    public void test() {
        System.out.println("animal invoke");
    }
}

class Dog extends Animal {
    @Override
    public void test() {
        System.out.println("dog invoke");
    }
}

class Cat extends Animal {
    @Override
    public void test() {
        System.out.println("cat invoke");
    }
}

复写对应的字节码指令,是按照多态性来执行的。具体操作是:从栈顶返回出类的实际类型,然后调用实际类型对应的方法秒描述符一样的方法,且同时要满足权限,如果实际类型不匹配,再往上找对象的父类,这就是多态的体现,找的是类的实际类型

看对应的字节码指令:

$2.4、方法在字节码中的调用形式及重载和复写的区别

虚方法中虽然是申明父类Anumal,但是执行时返回的是类的实际类型:Animal a1=new Dog(),真正执行的是Dog类的test方法,因为dog里的test方法描述符和权限都满足;以上就是在多态情况下,对方法的字节码动态查找。

总结

1、重载,是静态的,找的是类的申明类型

2、复写,是动态的,找的是类的实际类型,跟多态有关。

以上两者都是为方法的调用者决定的。

 

相关标签: jvm#字节码文件