调用方法

来源:互联网 发布:linux tar命令安装 编辑:程序博客网 时间:2024/06/06 01:14

方法调用

  方法调用是指确定被调用方法的版本(即调用哪一个方法),而不是具体方法里面代码的执行。
  Java程序编译后生成的是Class文件,Class文件的编译过程并不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都是符号引用,而不是方法在实际运行时内存布局中的入口地址。这也使得Java的方法调用过程变得相对复杂起来,有些方法在类加载过程的解析阶段被确定,有些甚至要等到实际运行时才能确定

解析

  非虚方法:在类加载的解析阶段就可以确定调用版本(在解析阶段将符号引用转化为直接引用)的方法,只要能被invokestatic和invokespecial指令调用的犯法,都可以在解析阶段中确定唯一的调用版本,包括**静态方法、私有方法、实例构造器、父类方法**4类。final方法也是非虚方法,但它是使用invokevirtual指令来调用的。

分派

  分派和解析不是两个互斥阶段,而是两种确定方法调用的策略,分派也会发生在解析阶段,比如当静态方法有多个重载版本,就需要使用到静态分派。

静态分派(确定多个重载方法的调用版本)

  假设Human是Man的父类,Human human = new Man(),这个语句中human是静态类型,Man是实际类型。当方法被重载时,比如有sayHello(Human)和sayHello(Man)两个方法,之后调用sayHello(human),则会调用第一个方法,而不是第二个方法。在编译期,Java编译期会根据参数的静态类型决定使用哪个重载版本。所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。

动态分派(确定多个重写(override)方法的调用版本)

  invokevirtual: 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是 Java 语言中最常见的方法分派方式。
  每次调用invokevirtual指令时,会先找到从操作数栈顶的第一个元素所指向的对象的实际类型,然后C里面找描述符和简单名称都相符的方法,并进行访问权限校验,如果校验失败则抛出异常,如果找不到相符的方法,则按照继承关系从下往上找,直到找到方法为止,找不到则抛出java.lang.AbstractMethodError异常。

单分派和多分派

  方法的接收者和参数都称为宗量,在编译期的静态分派中,JVM是根据方法接受者和参数的静态类型来确定方法(两个宗量),并生成invokevirtual指令,之后在运行期的动态分派中,JVM是根据方法的接收者的实际类型来却确定最终执行方法(一个宗量),所以Java是一门静态多分派、动态单分派的语言。

动态类型语言支持

  动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期,JavaScript、Python、Lua都是动态类型语言,Java和C++都是静态类型语言。
  在Java中的调用方法语句在编译期会生成一个方法符号引用放在class常量池里,作为invokevirtual、invokespecial、invokestatic、invokeinterface指令的参数,比如println(String)会生成符号引用java/io/PringStream.println:(Ljava/lang/String)V。

java.lang.invoke包

  JDK1.7实现了JSP-292,新加入的java.lang.invoke包就是JSR-292的一个重要组成部分,这个包的主要目的是在之前单纯依靠符号引用来确定调用的目标方法这种方式以外,提供一种新的动态确定目标方法的机制,称为MethodHandle
  

invokedynamic指令

  invokedynamic指令的参数是JDK1.7新加入的CONSTANT_InvokeDynamic_info常量,这个常量有三个信息:引导方法、方法类型和名称,引导方法的返回值是java.lang.invoke.CallSite对象,这个代表真正要执行的目标方法调用。根据CONSTANT_InvokeDynamic_info常量中提供的信息,虚拟机可以找到并且执行引导方法,从而获得一个CallSite对象,最终调用要执行的目标方法。

原创粉丝点击