zoukankan      html  css  js  c++  java
  • 方法调用

    解析

    所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析(Resolution)。

    在Java语言中符合“编译期可知,运行期不可变”这个要求的方法,主要包括静态方法和
    私有方法两大类。

    与之相对应的是,在Java虚拟机里面提供了5条方法调用字节码指令,分别如下。
    1. invokestatic:调用静态方法。
    2. invokespecial:调用实例构造器<init>方法、私有方法和父类方法。
    3. invokevirtual:调用所有的虚方法。
    4. invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
    5. invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方
    法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

    只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,与之相反,其他方法称为虚方法。

    Java中的非虚方法除了使用invokestatic、invokespecial调用的方法之外还有一种,就是被final修饰的方法。虽然final方法是使用invokevirtual指令来调用的,但是由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,又或者说多态选择的结果肯定是唯一的。在Java语言规范中明确说明了final方法是一种非虚方法。

    分派

    静态分派

    所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。

    静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。另外,编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不是“唯一的”,往往只能确定一个“更加合适的”版本。

    public class StaticDispatch {
    
        static abstract class Human{
    
        }
    
        static class Man extends Human{
    
        }
    
        static class Woman extends Human{
    
        }
    
        public void sayHello(Human guy){
            System.out.println("hello,guy!");
        }
    
        public void sayHello(Man guy){
            System.out.println("hello,gentleman!");
        }
    
        public void sayHello(Woman guy){
            System.out.println("hello,lady!");
        }
    
        public static void main(String[] args) {
            Human man = new Man();
            Human woman = new Woman();
            StaticDispatch staticDispatch = new StaticDispatch();
            staticDispatch.sayHello(man);
            staticDispatch.sayHello(woman);
        }
    
    }
      public static void main(java.lang.String[]);
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=4, args_size=1
             0: new           #7                  // class com/fcs/jvms/StaticDispatch$Man
             3: dup
             4: invokespecial #8                  // Method com/fcs/jvms/StaticDispatch$Man."<init>":(
    
             7: astore_1
             8: new           #9                  // class com/fcs/jvms/StaticDispatch$Woman
            11: dup
            12: invokespecial #10                 // Method com/fcs/jvms/StaticDispatch$Woman."<init>")V
            15: astore_2
            16: new           #11                 // class com/fcs/jvms/StaticDispatch
            19: dup
            20: invokespecial #12                 // Method "<init>":()V
            23: astore_3
            24: aload_3
            25: aload_1
            26: invokevirtual #13                 // Method sayHello:(Lcom/fcs/jvms/StaticDispatch$Hum;)V
            29: aload_3
            30: aload_2
            31: invokevirtual #13                 // Method sayHello:(Lcom/fcs/jvms/StaticDispatch$Hum;)V
            34: return
          LineNumberTable:
            line 36: 0
            line 37: 8
            line 38: 16
            line 39: 24
            line 40: 29
            line 41: 34
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0      35     0  args   [Ljava/lang/String;
                   8      27     1   man   Lcom/fcs/jvms/StaticDispatch$Human;
                  16      19     2 woman   Lcom/fcs/jvms/StaticDispatch$Human;
                  24      11     3 staticDispatch   Lcom/fcs/jvms/StaticDispatch;

    动态分派

    它和多态性的另外一个重要体现——重写/覆盖(Override)有着很密切的关联。

    public class DynamicDispatch {
    
        static abstract class Human{
            protected abstract void sayHello();
        }
    
        static class Man extends Human{
    
            @Override
            protected void sayHello() {
                System.out.println("man say hello");
            }
        }
    
        static class Woman extends Human{
    
            @Override
            protected void sayHello() {
                System.out.println("woman say hello");
            }
        }
    
        public static void main(String[] args) {
            Human man = new Man();
            Human woman = new Woman();
            man.sayHello();
            woman.sayHello();
            man = new Woman();
            man.sayHello();
        }
    }

    查看字节码,编译阶段看不出任何区别

      public static void main(java.lang.String[]);
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=3, args_size=1
             0: new           #2                  // class com/fcs/jvms/DynamicDispatch$Man
             3: dup
             4: invokespecial #3                  // Method com/fcs/jvms/DynamicDispatch$Man."<init>":()
    V
             7: astore_1
             8: new           #4                  // class com/fcs/jvms/DynamicDispatch$Woman
            11: dup
            12: invokespecial #5                  // Method com/fcs/jvms/DynamicDispatch$Woman."<init>":
    ()V
            15: astore_2
            16: aload_1
            17: invokevirtual #6                  // Method com/fcs/jvms/DynamicDispatch$Human.sayHello:
    ()V
            20: aload_2
            21: invokevirtual #6                  // Method com/fcs/jvms/DynamicDispatch$Human.sayHello:
    ()V
            24: new           #4                  // class com/fcs/jvms/DynamicDispatch$Woman
            27: dup
            28: invokespecial #5                  // Method com/fcs/jvms/DynamicDispatch$Woman."<init>":
    ()V
            31: astore_1
            32: aload_1
            33: invokevirtual #6                  // Method com/fcs/jvms/DynamicDispatch$Human.sayHello:
    ()V
            36: return
          LineNumberTable:
            line 30: 0
            line 31: 8
            line 32: 16
            line 33: 20
            line 34: 24
            line 35: 32
            line 36: 36
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0      37     0  args   [Ljava/lang/String;
                   8      29     1   man   Lcom/fcs/jvms/DynamicDispatch$Human;
                  16      21     2 woman   Lcom/fcs/jvms/DynamicDispatch$Human;

    invokevirtual指令的运行时解析过程大致分为以下几个步骤:

    1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。

    2. 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校
      验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回
      java.lang.IllegalAccessError异常。

    3. 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。

    4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

    由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,这个过程就是Java语言中方法重写的本质。

    单分派与多分派

    Java语言是一门静态多分派、动态单分派的语言。

    虚拟机动态分派的实现

  • 相关阅读:
    【论文阅读】A practical algorithm for distributed clustering and outlier detection
    第11组 团队Git现场编程实战
    第11组 团队项目-需求分析报告
    团队项目-选题报告
    第二次结对编程作业
    第11组 团队展示
    第一次结对编程作业
    第一次个人编程作业
    第一次博客作业
    (转)script标签到底该放在哪里
  • 原文地址:https://www.cnblogs.com/lucare/p/8679138.html
Copyright © 2011-2022 走看看